How to make the for each loop function in C++ work with a custom class

前端 未结 5 1215
遇见更好的自我
遇见更好的自我 2020-12-05 04:18

I\'m new to C/C++ programming, but I\'ve been programming in C# for 1.5 years now. I like C# and I like the List class, so I thought about making a List class in C++ as an e

相关标签:
5条回答
  • 2020-12-05 04:27

    Let iterable be of type Iterable. Then, in order to make

    for (Type x : iterable)
    

    compile, there must be types called Type and IType and there must be functions

    IType Iterable::begin()
    IType Iterable::end()
    

    IType must provide the functions

    Type operator*()
    void operator++()
    bool operator!=(IType)
    

    The whole construction is really sophisticated syntactic sugar for something like

    for (IType it = iterable.begin(); it != iterable.end(); ++it) {
        Type x = *it;
        ...
    }
    

    where instead of Type, any compatible type (such as const Type or Type&) can be used, which will have the expected implications (constness, reference-instead-of-copy etc.).

    Since the whole expansion happens syntactically, you can also change the declaration of the operators a bit, e.g. having *it return a reference or having != take a const IType& rhs as needed.

    Note that you cannot use the for (Type& x : iterable) form if *it does not return a reference (but if it returns a reference, you can also use the copy version).

    Note also that operator++() defines the prefix version of the ++ operator -- however it will also be used as the postfix operator unless you explicitly define a postfix ++. The ranged-for will not compile if you only supply a postfix ++, which btw.can be declared as operator++(int) (dummy int argument).


    Minimal working example:

    #include <stdio.h>
    typedef int Type;
    
    struct IType {
        Type* p;
        IType(Type* p) : p(p) {}
        bool operator!=(IType rhs) {return p != rhs.p;}
        Type& operator*() {return *p;}
        void operator++() {++p;}
    };
    
    const int SIZE = 10;
    struct Iterable {
        Type data[SIZE];
    
        IType begin() {return IType(data); }
        IType end() {return IType(data + SIZE);}
    };
    
    Iterable iterable;
    
    int main() {
        int i = 0;
        for (Type& x : iterable) {
            x = i++;
        }
        for (Type x : iterable) {
            printf("%d", x);
        }
    }
    

    output

    0123456789
    

    You can fake the ranged-for-each (e.g. for older C++ compilers) with the following macro:

     #define ln(l, x) x##l // creates unique labels
     #define l(x,y)  ln(x,y)
     #define for_each(T,x,iterable) for (bool _run = true;_run;_run = false) for (auto it = iterable.begin(); it != iterable.end(); ++it)\
         if (1) {\
             _run = true; goto l(__LINE__,body); l(__LINE__,cont): _run = true; continue; l(__LINE__,finish): break;\
             } else\
                while (1)   \
                    if (1) {\
                        if (!_run) goto l(__LINE__,cont);/* we reach here if the block terminated normally/via continue */   \
                        goto l(__LINE__,finish);/* we reach here if the block terminated by break */\
                    }   \
                    else\
                    l(__LINE__,body): for (T x = *it;_run;_run=false) /* block following the expanded macro */                         
    
     int main() {
         int i = 0;
         for_each(Type&, x, iterable) {
             i++;
             if (i > 5) break;
             x = i;
         }
         for_each(Type, x, iterable) {
             printf("%d", x);
         }
         while (1);
     }
    

    (use declspec or pass IType if your compiler doesn't even have auto).

    Output:

     1234500000
    

    As you can see, continue and break will work with this thanks to its complicated construction. See http://www.chiark.greenend.org.uk/~sgtatham/mp/ for more C-preprocessor hacking to create custom control structures.

    0 讨论(0)
  • 2020-12-05 04:31

    That syntax Intellisense suggested is not C++; or it's some MSVC extension.

    C++11 has range-based for loops for iterating over the elements of a container. You need to implement begin() and end() member functions for your class that will return iterators to the first element, and one past the last element respectively. That, of course, means you need to implement suitable iterators for your class as well. If you really want to go this route, you may want to look at Boost.IteratorFacade; it reduces a lot of the pain of implementing iterators yourself.

    After that you'll be able to write this:

    for( auto const& l : ls ) {
      // do something with l
    }
    

    Also, since you're new to C++, I want to make sure that you know the standard library has several container classes.

    0 讨论(0)
  • 2020-12-05 04:32

    C++ does not have the for_each loop feature in its syntax. You have to use c++11 or use the template function std::for_each.

    #include <vector>
    #include <algorithm>
    #include <iostream>
    
    struct Sum {
        Sum() { sum = 0; }
        void operator()(int n) { sum += n; }
    
        int sum;
    };
    
    int main()
    {
        std::vector<int> nums{3, 4, 2, 9, 15, 267};
    
        std::cout << "before: ";
        for (auto n : nums) {
            std::cout << n << " ";
        }
        std::cout << '\n';
    
        std::for_each(nums.begin(), nums.end(), [](int &n){ n++; });
        Sum s = std::for_each(nums.begin(), nums.end(), Sum());
    
        std::cout << "after:  ";
        for (auto n : nums) {
            std::cout << n << " ";
        }
        std::cout << '\n';
        std::cout << "sum: " << s.sum << '\n';
    }
    
    0 讨论(0)
  • 2020-12-05 04:36

    Firstly, the syntax of a for-each loop in C++ is different from C# (it's also called a range based for loop. It has the form:

    for(<type> <name> : <collection>) { ... }
    

    So for example, with an std::vector<int> vec, it would be something like:

    for(int i : vec) { ... }
    

    Under the covers, this effectively uses the begin() and end() member functions, which return iterators. Hence, to allow your custom class to utilize a for-each loop, you need to provide a begin() and an end() function. These are generally overloaded, returning either an iterator or a const_iterator. Implementing iterators can be tricky, although with a vector-like class it's not too hard.

    template <typename T>
    struct List
    {
        T* store;
        std::size_t size;
        typedef T* iterator;
        typedef const T* const_iterator;
    
        ....
    
        iterator begin() { return &store[0]; }
        const_iterator begin() const { return &store[0]; }
        iterator end() { return &store[size]; }
        const_iterator end() const { return &store[size]; }
    
        ...
     };
    

    With these implemented, you can utilize a range based loop as above.

    0 讨论(0)
  • 2020-12-05 04:51

    As @yngum suggests, you can get the VC++ for each extension to work with any arbitrary collection type by defining begin() and end() methods on the collection to return a custom iterator. Your iterator in turn has to implement the necessary interface (dereference operator, increment operator, etc). I've done this to wrap all of the MFC collection classes for legacy code. It's a bit of work, but can be done.

    0 讨论(0)
提交回复
热议问题