0

I am using g++ v8.2 with -std=c++2a -Wall- -Wextra -pedantic running on a Raspberry Pi 3B+. I am trying to better understand the range based for loop for my custom containers.

Below you see a class that implements a custom class with a ranged based for. That is working fine. However. I have some additional specific questions to my working implementation.

I checked already also this and others. But none of those answer my questions.

Here is the code:

#include <iostream>
struct LinkedList  // Simplest linked list
{
    int k;  
    LinkedList *next;
};
// For test pruposes: Build manually linked list as globals
LinkedList aa3{3,nullptr};  LinkedList aa2{2,&aa3};  LinkedList aa1{1,&aa2};

class Example
{
    public:
        Example    &begin       (void)       { linkedList = linkedListStartAddress; return *this;}
        int         end         (void)       { return 0; }  
        LinkedList &operator *  (void)       { return *linkedList; }
        void        operator ++ (void)       { linkedList = linkedList->next; }
        bool        operator != (const int&) { return (linkedList != nullptr);}
    protected:
        LinkedList *linkedListStartAddress {&aa1}; // Global initialisation for test purposes
        LinkedList *linkedList{&aa1};              // Global initialisation for test purposes
 };

int main(void)
{
    Example example;
    for (auto l : example)
    {
        std::cout << l.k << '\n';
    }
    return 0;
}

OK, this works.

The general definition for the loop is:

{
    auto && __range = range_expression ;
    auto __begin = begin_expr;
    auto __end = end_expr
    for (;__begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

This is hard to understand. Neither __range, nor range_expression are used later on. 'auto' is also difficult for me in that context. I cannot see the type. Now my assumptions and questions.

  1. The example class is container and iterator at the same time. Is this a correct or common use case?
  2. Obviously the return type of 'begin' must be the type of the 'range_expression' or vice versa. Correct?
  3. The return type of 'begin' must be the class type, otherwise the operators (++, *, !=) would not be called. Correct?
  4. The 'begin' function is called just for initialisation purposes. And to return a reference to the class. It has not necessarily a relation to the underlying container. Correct?
  5. The 'end' function (I checked: It is called immediately after 'begin'. Then never again) has no meaning (c++17). It is just there to define (with its return value) the type for the right hand side of the 'operator !='. Correct?
  6. How can I deduce the type for the 'operator !=' to be a "const 'ReturnTypeOfEnd' &". I tried with decltype, but I failed.
  7. Obviously the return type of the 'operator*' defines the type of the range_declaration. In my case '*__begin' has a total different type than '__begin'. Not some address or something. OK?

I would be happy, if you could shed some light on this . . .

A M
  • 14,694
  • 5
  • 19
  • 44
  • `auto && __range = range_expression` just means that the range expression gets evaluated. That's the "y" part of "`for (x:y)`". You can think of `begin_expr` and `end_expr` as, basically, `std::begin(__range)` and `std::end(__range)`. That, pretty much, is the capsule summary, in 600 characters or less. – Sam Varshavchik Dec 24 '18 at 17:22
  • Duplicate of https://stackoverflow.com/q/8164567/2466431? – JVApen Dec 24 '18 at 17:35
  • Possible duplicate of [How to make my custom type to work with "range-based for loops"?](https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops) – JVApen Dec 24 '18 at 17:35
  • I read both posts before asking my questions. When I typed in the headline, those posts have been shown as possible answers. But for me the topic is still not clear, hence I asked specifically. Would be happy to get some additional concrete answers. Thanks – A M Dec 24 '18 at 17:58
  • @Armin: There are *way* too many separate, unconnected questions in this question. If something isn't clear, as a question about that specific thing. What you're wanting is really a step-by-step re-explanation of how range `for` works, only limited to this *specific* use case. – Nicol Bolas Dec 24 '18 at 20:49

1 Answers1

1

The main problem with your solution is that you try to so things in a non standard way which has many limitations and might make it hard to use by expert who have a good understanding how iterators works in STL.

I will try to answer most questions in a less technical way that should help understand how it works in practice.

1) Sharing the type will lead to some problem as some algorithms might need more than one active iterator. Also, it does not respect the SRP (single responsibility principle) and thus is not a good design practice.

2) It only need to return something that essentially behave like an iterator.

3) Or it could be a pointer if the data is contiguous in memory.

4) Usually the begin function returns an iterator by value.

5) Usually the end function returns an iterator to the end. It could also returns a sentinel object if the end is not really a position (for ex. input stream or if the last value of the container is a sentry).

6) You could put your own typedef/aliases inside the class and use them as appropriate.

7) The return value type of operator * will almost always be different than the one of the iterator type.

Then some remarks/suggestions

  • In you case LinkedList would be the iterator type (or you would use a wrapper around that).
  • Often you want you container to be more than an iterator if you want to be able to know the size for example without having to iterate the whole list. Also the container might provide some optimized member function like sort in std::list.
  • STL source code might be a good source if you want to know how expert do it.
  • Your code does not respect constness and thus would not work if you replace Example example; with const Example example;.
  • Most of your operators does not follows usual convention in their declaration.
  • Your begin function has side effect.
  • With your code, you won't be able to store an iterator to a position in your list. This mean that algorithm like sorting or erasing matching items will probably fails.
  • Putting void inside empty parameter list is essentially an obsolete way to write code that should not be used anymore in C++. It only has useful purpose in C.
  • Operator++ should usually return a reference to this.
  • If you want to use a sentry for the end value, it is better to use your own type (an enum value could do). Otherwise, it would allows unexpected comparison to compile like example != 25 (and in that case, one might thing that the loop would end when the value of k is 25) so it make the code harder to understand.
  • Why not use std::forward_list instead of reinventing the wheel.
  • If you really need to use your LinkedList then STL one could be a valuable source of information on how to properly define iterators.
Phil1970
  • 2,605
  • 2
  • 14
  • 15
  • Thank you very much for your evaluation and your hints. Just for clarification: The provided code is just an example. Purely for test purposes. I wanted to understand how the range based for works in the background. Thanks again – A M Dec 24 '18 at 23:07