325

Like many people these days I have been trying the different features that C++11 brings. One of my favorites is the "range-based for loops".

I understand that:

for(Type& v : a) { ... }

Is equivalent to:

for(auto iv = begin(a); iv != end(a); ++iv)
{
  Type& v = *iv;
  ...
}

And that begin() simply returns a.begin() for standard containers.

But what if I want to make my custom type "range-based for loop"-aware?

Should I just specialize begin() and end()?

If my custom type belongs to the namespace xml, should I define xml::begin() or std::begin() ?

In short, what are the guidelines to do that?

Julian
  • 4,170
  • 4
  • 20
  • 27
ereOn
  • 53,676
  • 39
  • 161
  • 238
  • 1
    It is possible either by defining a member `begin/end` or a friend, static or free `begin/end`. Just be careful in which namespace you put the free function: http://stackoverflow.com/questions/28242073/viewing-a-raw-pointer-as-a-range-in-range-based-for-loop – alfC Mar 30 '16 at 21:06
  • Could anyone please post an answer with the example of a float value range which is NOT a container: ``for( auto x : range(0,TWO_PI, 0.1F) ) { ... }``. I am curious how you work around the fact that `´operator!=()`` is hard to define. And what about the dereferencing (``*__begin``) in this case? I think it would be a great contribution if someone showed us how *that* is done! – BitTickler Oct 13 '18 at 23:57

10 Answers10

265

The standard has been changed since the question (and most answers) were posted in the resolution of this defect report.

The way to make a for(:) loop work on your type X is now one of two ways:

  • Create member X::begin() and X::end() that return something that acts like an iterator

  • Create a free function begin(X&) and end(X&) that return something that acts like an iterator, in the same namespace as your type X

And similar for const variations. This will work both on compilers that implement the defect report changes, and compilers that do not.

The objects returned do not have to actually be iterators. The for(:) loop,

for( range_declaration : range_expression )

unlike most parts of the C++ standard, is specified to expand to something equivalent to:

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

where the variables beginning with __ are for exposition only, and begin_expr and end_expr is the magic that calls begin/end

The requirements on the begin/end return value are simple: You must overload pre-++, ensure the initialization expressions are valid, binary != that can be used in a boolean context, unary * that returns something you can assign-initialize range_declaration with, and expose a public destructor.

Doing so in a way that isn't compatible with an iterator is probably a bad idea, as future iterations of C++ might be relatively cavalier about breaking your code if you do.

As an aside, it is reasonably likely that a future revision of the standard will permit end_expr to return a different type than begin_expr. This is useful in that it permits "lazy-end" evaluation (like detecting null-termination) that is easy to optimize to be as efficient as a hand-written C loop, and other similar advantages.


¹ Note that for(:) loops store any temporary in an auto&& variable, and pass it to you as an lvalue. You cannot detect if you are iterating over a temporary (or other rvalue); such an overload will not be called by a for(:) loop. See [stmt.ranged] 1.2-1.3 from n4527.

² Either call the begin/end method, or ADL-only lookup of free function begin/end, or magic for C-style array support. Note that std::begin is not called unless range_expression returns an object of type in namespace std or dependent on same.


In the range-for expression has been updated

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

with the types of __begin and __end have been decoupled.

This permits the end iterator to not be the same type as begin. Your end iterator type can be a "sentinel" which only supports != with the begin iterator type.

A practical example of why this is useful is that your end iterator can read "check your char* to see if it points to '0'" when == with a char*. This allows a C++ range-for expression to generate optimal code when iterating over a null-terminated char* buffer.

struct null_sentinal_t {
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(Rhs const& ptr, null_sentinal_t) {
    return !*ptr;
  }
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
    return !(ptr==null_sentinal_t{});
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(null_sentinal_t, Lhs const& ptr) {
    return !*ptr;
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
    return !(null_sentinal_t{}==ptr);
  }
  friend bool operator==(null_sentinal_t, null_sentinal_t) {
    return true;
  }
  friend bool operator!=(null_sentinal_t, null_sentinal_t) {
    return false;
  }
};

live example of this.

Minimal test code is:

struct cstring {
  const char* ptr = 0;
  const char* begin() const { return ptr?ptr:""; }// return empty string if we are null
  null_sentinal_t end() const { return {}; }
};

cstring str{"abc"};
for (char c : str) {
    std::cout << c;
}
std::cout << "\n";

Here is a simple example.

namespace library_ns {
  struct some_struct_you_do_not_control {
    std::vector<int> data;
  };
}

Your code:

namespace library_ns {
  int* begin(some_struct_you_do_not_control& x){ return x.data.data(); }
  int* end(some_struct_you_do_not_control& x){ return x.data.data()+x.data.size(); }
  int const* cbegin(some_struct_you_do_not_control const& x){ return x.data.data(); }
  int* cend(some_struct_you_do_not_control const& x){ return x.data.data()+x.data.size(); }
  int const* begin(some_struct_you_do_not_control const& x){ return cbegin(x); }
  int const* end(some_struct_you_do_not_control const& x){ return cend(x); }
}

this is an example how you can augment a type you don't control to be iterable.

Here I return pointers-as-iterators, hiding the fact I have a vector under the hood.

For a type you do own, you can add methods:

struct egg {};
struct egg_carton {
  auto begin() { return eggs.begin(); }
  auto end() { return eggs.end(); }
  auto cbegin() const { return eggs.begin(); }
  auto cend() const { return eggs.end(); }
  auto begin() const { return eggs.begin(); }
  auto end() const { return eggs.end(); }
private:
  std::vector<egg> eggs;
};

here I reuse the vector's iterators. I use auto for brevity; in I'd have to be more verbose.

Here is a quick and dirty iterable range-view:

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }

  std::size_t size() const
  // C++20 only line: (off C++20 it generates a hard error)
  requires std::random_access_iterator<It>
  {
    return end()-begin(); // do not use distance: O(n) size() is toxic
  }

  bool empty() const { return begin()==end(); }
 
  range_t without_back() const {
    if(emptty()) return *this;
    return {begin(), std::prev(end())};
  }

  range_t without_back( std::size_t n ) const
  // C++20 only line: (see below)
  requires !std::random_access_iterator<It>
  {
    auto r=*this;
    while(n-->0 && !r.empty())
      r=r.without_back();
    return r;
  }

  range_t without_front() const {
    if(empty()) return *this;
    return {std::next(begin()), end()};
  }

  range_t without_front( std::size_t n ) const
  // C++20 only line: (see below)
  requires !std::random_access_iterator<It>
  {
    auto r=*this;
    while(n-->0 && !r.empty())
      r=r.without_front();
    return r;
  }

  // C++20 section:
  range_t without_back( std::size_t n ) const
  requires std::random_access_iterator<It>
  {
    n = (std::min)(n, size());
    return {b, e-n};
  }
  range_t without_front( std::size_t n ) const
  requires std::random_access_iterator<It>
  {
    n = (std::min)(n, size());
    return {b+n, e};
  }
  // end C++20 section


  decltype(auto) front() const { return *begin(); }
  decltype(auto) back() const { return *(std::prev(end())); }
};
template<class It>
range_t(It,It)->range_t<It>;
template<class C>
auto make_range( C&& c ) {
  using std::begin; using std::end;
  return range_t{ begin(c), end(c) };
}

using template class deduction.

std::vector<int> v{1,2,3,4,5};
for (auto x : make_range(v).without_front(2) ) {
  std::cout << x << "\n";
}

prints 3 4 5, skipping first 2.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • If range-based for uses a different lookup mechanism, then maybe it's possible to arrange that range-based for gets a different pair of `begin` and `end` functions than is available in normal code. Perhaps they could then be very specialized to behave differently (i.e. faster by ignoring the end argument to get the maximize optimizations possible.) But I'm not good enough with namespaces to be sure how to do this. – Aaron McDaid Aug 20 '15 at 23:11
  • @AaronMcDaid not very practical. You'd easily end up with surprising results, because some means of calling begin/end would end up with the range-based for begin/end, and others would not. Innocuous changes (from the client side) would get behavior changes. – Yakk - Adam Nevraumont Aug 21 '15 at 00:52
  • 1
    You don't need `begin(X&&)`. The temporary is suspended in midair by `auto&&` in a range-based for, and `begin` is always called with an lvalue (`__range`). – T.C. Nov 02 '15 at 18:12
  • 12
    This answer would really benefit from a template example that one can copy and implement. – Tomáš Zato Aug 26 '19 at 11:48
  • 1
    I'd rather put stress on the properties of the iterator type(*,++,!=). I should ask you to rephrase this reply to make the iterator type specs bolder. – Red.Wave Mar 03 '20 at 15:37
  • @Red.Wave I disagree. The first thing you have to do is the begin/end support (which can be done 2 ways), and *then* you have to worry about the type you return. In 9/10 cases I do this, I return some previously defined iterator, because I'm actually wrapping another container or a raw buffer. I will admit this isn't "write a range for loop 101"; I wrote this answer to be comprehensive undertandable full explanation of what the C++ language rules are, not a simple set of rules-of-thumb for a novice. csjpeter below https://stackoverflow.com/a/28926968/1774667 has a simple example. – Yakk - Adam Nevraumont Mar 03 '20 at 18:41
  • An explicit answer to the OP would give the short list IMO and expand on it. This answer has just implicitly addressed the question. OP is asking "what else than begin/end". So the forward iterator properties is the most relevant part of the answer. – Red.Wave Mar 04 '20 at 14:49
  • @Red.Wave No, you don't need to implement a forward iterator in order to make `for(:)` loops work. You need to implement exactly the operations I described, and it needs to work in exactly the generated code I posted. I also mentioned you *should* probably use real iterators, but that is not an actual requirement. Nor do you have to implement your own; you can reuse existing ones (such as pointers, or from another container). The core is the begin/end, and understanding that `for(:)` is defined much like a LISP macro. – Yakk - Adam Nevraumont Mar 04 '20 at 15:29
  • I disagree. Who talks about implementation? 'begin' shall return a forward iterator. And anyone who trying to play with range based for loop must be aware of forward iterator properties. Reuse or reimplementation is a design choice. Either way, one should be aware of the requirements. And IMO those requirements need to be explicitly stated. – Red.Wave Mar 04 '20 at 21:27
  • Without s forward iterator you cannot even write the declarator for the loop variable. – Red.Wave Mar 05 '20 at 04:44
  • 1
    @red.w You are wrong. Please learn what the term "forward iterator" means in C++ (it refers to a very specific concept). You do not need a "forward iterator" to call a `for(:)` loop. You need a type that supports exactly the operations I describe above, which a forward iterator *does*, but a type that is not a "forward iterator" can also. `struct bob { int operator*()const{return 0;} void operator++()const{} bool operator!=(bob){return true;} };` does not qualify as a forward iterator but will let you write a `for(:)` loop. Repeating false statements does not make them true. – Yakk - Adam Nevraumont Mar 05 '20 at 10:52
  • https://en.cppreference.com/w/cpp/experimental/ranges/iterator/ForwardIterator – Red.Wave Mar 05 '20 at 14:34
  • I know exactly what I am discussing. – Red.Wave Mar 05 '20 at 14:35
  • @red.wave You can write your own `for(:)` loop without a ForwardIterator. There is no requirement that the return value of `begin` is a forward iterator in the C++ standard. "Without s forward iterator you cannot even write the declarator for the loop variable" is simply **false**. I am uninterested in adding false statements to my answer. It is difficult to discuss this when you keep claiming false things, so unless you stop this conversation is pointless. If you are confused about the relationship between ForwardIterator and `for(:)` loops feel free to press [Ask Question] button. – Yakk - Adam Nevraumont Mar 06 '20 at 09:48
  • A simple pointer meets the requirements of foward iterator which is what range based for needs. You need not explicitly tag the type as 'foward iterator' like STL does; nevertheless the type used shall satisfy forward iterator's conceptual requirements (*, ++, !=). – Red.Wave Mar 06 '20 at 16:03
  • Input iterator is what I should've mentioned. Forward iterator is a bit more restrictive. – Red.Wave Mar 06 '20 at 16:23
  • This is more complicated than it is useful. – Zac Taylor Apr 21 '20 at 14:47
  • 1
    @ZacTaylor It isn't intended for a non-C++ programmer. It is intended to tell the truth. I could lie to you. I could write boilerplate code you could copy-paste and not understand. But if you are writing your own, you should know everything up to the first "div" above. In certain cases (where you are wrapping another container, or part of another container) you need only know the first 3 paragraphs and 2 bullets. – Yakk - Adam Nevraumont Apr 21 '20 at 16:38
  • @Yakk-AdamNevraumont, I see the importance of the technical details but I think a clear "boiler plate" example is worth a thousand words... Maybe a good old fashioned TL;DR at the top would make your answer more digestible to those trying to learn both the boiler plate and the technicalities. – Zac Taylor Apr 21 '20 at 19:37
  • this live example no longer exists – Max Sep 18 '20 at 04:08
  • 1
    @Max Fixed, updated with C++17 compliant compiler so the loop isn't manually expanded, code to reproduce live example included in answer. – Yakk - Adam Nevraumont Sep 21 '20 at 15:18
  • I think reading this has now convinced me c++ isn't for me. Blimey. Convoluted isn't the word. – RichieHH Sep 07 '21 at 20:44
  • @RichieHH Fair enough. To be clear, however, the stuff I'm doing above is something professional C++ programmer with 10+ years of experience may never have touched, because there was no need. There is depth in C++, and you don't have to swim down into it for 99.9% of your code. In Java, the fact you can write a bytecode assembler to convert your lua-style scripting code into 'native' java, load it dynamically, replace the gc so you can unload the script when it needs replacing, and other crazy stuff is not something you need care about either. – Yakk - Adam Nevraumont Sep 07 '21 at 20:58
  • @Yakk-AdamNevraumont why are RHS and LHS seemingly reversed in the ```null_sentinel``` operators? ```friend bool operator==(Rhs const& ptr, null_sentinal_t) { return !*ptr; }``` – user3882729 Oct 15 '21 at 08:46
  • @user just an amazingly poor typo then a bunch of copy paste – Yakk - Adam Nevraumont Oct 15 '21 at 11:43
  • I can't get the very last example of _"using [tag:c++17] template class deduction"_ working. It only works in c++20 mode in g++ but g++ c++17 won't accept it. clang++ (trunk) refuses it in all modes. – Ted Lyngmo Mar 05 '22 at 12:06
  • @TedLyngmo It might need a constructor deduction hint. Also, the price of using prev/next/distance is the operations go from O(1) if they work to O(n) sometimes; I honestly prefer them breaking to them being O(n). For without back/front a 0-arg thst uses prev/next is justified; maybe even in general (ouch), but any size that is O(n) is toxic. – Yakk - Adam Nevraumont Mar 05 '22 at 12:19
  • @Yakk-AdamNevraumont _" deduction hint_" - Yes, I think so too. _"O(n)_" - True. I personally think that the user should be able to make the choice if it's worth it or not, but I won't mind if you rollback my edit. – Ted Lyngmo Mar 05 '22 at 12:29
  • @Yakk-AdamNevraumont Your edit makes it _always_ O(n) but with `std::next` and `std::prev` you'd have O(1) for _LegacyRandomAccessIterator_ s. – Ted Lyngmo Mar 05 '22 at 12:35
  • @TedLyngmo yep; assuming, of course, optimizer cannot handle looped `--` into `-`. That was why original was written that way. A more robust version does trait matching and different implmentations. For `size()`, I hold that people calling it *are not thinking* it could cost O(n); if they want O(n) they can write distance thrmselves. I don't mind expensive operations (which you shouldn't be doing very often) being extra verbose. – Yakk - Adam Nevraumont Mar 05 '22 at 13:03
  • @Yakk-AdamNevraumont _"optimizer_" - Very good point. _"not thinking_" - Good point too :-) – Ted Lyngmo Mar 05 '22 at 13:10
  • @Yakk-AdamNevraumont, could you expand on your requirement (statement): "ensure the initialization expressions are valid"? Is this a separate requirement, or is it a clarification regarding the "You must overload pre-++" requirement? Thx :) – Morten Nov 05 '22 at 04:11
  • @morten Seperate. The range initialization expression is distinct from `++`. – Yakk - Adam Nevraumont Nov 05 '22 at 23:16
  • Could `auto cbegin() const { return eggs.begin(); } auto cend() const { return eggs.end(); }` be improved to `auto cbegin() const { return eggs.*c*begin(); } auto cend() const { return eggs.*c*end(); }` ? In any case, thanks for the examples! – HolKann Aug 29 '23 at 11:16
  • 1
    @HolKann I personally think `cbegin` is best treated as a short-cut to `as_const(foo).begin()` and not a separate function. But if your code base disagrees, feel free -- I'd add a lot of detection before I did that, as `cbegin()` is far rarer than `begin() const`. – Yakk - Adam Nevraumont Aug 29 '23 at 13:04
107

I write my answer because some people might be more happy with simple real life example without STL includes.

I have my own plain only data array implementation for some reason, and I wanted to use the range based for loop. Here is my solution:

template <typename DataType>
class PodArray {
public:
    class iterator {
    public:
        iterator(DataType * ptr): ptr(ptr){}
        iterator operator++() { ++ptr; return *this; }
        bool operator!=(const iterator & other) const { return ptr != other.ptr;  }
        const DataType& operator*() const { return *ptr; }
    private:
        DataType* ptr;
    };
private:
   unsigned len;
   DataType *val;
public:
   iterator begin() const { return iterator(val); }
   iterator end() const { return iterator(val + len); }
 
   // rest of the container definition not related to the question ...
};

Then the usage example:

PodArray<char> array;
// fill up array in some way
for(auto& c : array)
    printf("char: %c\n", c);
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
csjpeter
  • 1,721
  • 1
  • 14
  • 15
  • 8
    The example has the begin() and end() methods, and also have a basic (easy to understand) example iterator class that can easily be adjusted for any custom container type. Comparing std::array<> and any possible alternate implementation is a different question, and in my opinion has nothing to do with the range-based for loop. – csjpeter Mar 09 '15 at 06:26
  • This is a very concise and practical answer! It was exactly what I was looking for! Thanks! – Zac Taylor Apr 21 '20 at 14:53
  • 4
    Would it be more appropriate to remove the `const` return qualifier for `const DataType& operator*()`, and let the user choose to use `const auto&` or `auto&`? Thanks anyway, great answer ;) – Rick May 08 '20 at 09:56
  • `iterator operator++() { ++ptr; return *this; }` Why does this method return itself? It seems fine to change it like so: `void operator++() { ++ptr; }` . It works fine without any warnings or errors. – Mehrshad Farahani Nov 17 '21 at 19:14
  • 2
    @MehrshadFarahani: It might run but it is very unwise to do so as you expect the ++operator to return a reference to self, so `for (iterator i=0; i != 20; ++i);` will work but `for(iterator i=0; ++i != 20; );` won't. – glades Feb 20 '22 at 17:32
  • 1
    I'm curious in this example how does `len` get defined? – lurker Mar 26 '22 at 13:54
54

The relevant part of the standard is 6.5.4/1:

if _RangeT is a class type, the unqualified-ids begin and end are looked up in the scope of class _RangeT as if by class member access lookup (3.4.5), and if either (or both) finds at least one declaration, begin- expr and end-expr are __range.begin() and __range.end(), respectively;

— otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up with argument-dependent lookup (3.4.2). For the purposes of this name lookup, namespace std is an associated namespace.

So, you can do any of the following:

  • define begin and end member functions
  • define begin and end free functions that will be found by ADL (simplified version: put them in the same namespace as the class)
  • specialize std::begin and std::end

std::begin calls the begin() member function anyway, so if you only implement one of the above, then the results should be the same no matter which one you choose. That's the same results for ranged-based for loops, and also the same result for mere mortal code that doesn't have its own magical name resolution rules so just does using std::begin; followed by an unqualified call to begin(a).

If you implement the member functions and the ADL functions, though, then range-based for loops should call the member functions, whereas mere mortals will call the ADL functions. Best make sure they do the same thing in that case!

If the thing you're writing implements the container interface, then it will have begin() and end() member functions already, which should be sufficient. If it's a range that isn't a container (which would be a good idea if it's immutable or if you don't know the size up front), you're free to choose.

Of the options you lay out, note that you must not overload std::begin(). You are permitted to specialize standard templates for a user-defined type, but aside from that, adding definitions to namespace std is undefined behavior. But anyway, specializing standard functions is a poor choice if only because the lack of partial function specialization means you can only do it for a single class, not for a class template.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Aren't there certain requirements that the iterator much meet? ie be a ForwardIterator or something along those lines. – Pubby Dec 17 '12 at 07:44
  • 2
    @Pubby: Looking at 6.5.4, I think InputIterator is sufficient. But actually I don't think the type returned *has* to be an iterator at all for range-based for. The statement is defined in the standard by what it's equivalent to, so it's enough to implement only the expressions used in the code in the standard: operators `!=`, prefix `++` and unary `*`. It's probably *unwise* to implement `begin()` and `end()` member functions or non-member ADL functions that return anything other than an iterator, but I think it's legal. Specializing `std::begin` to return a non-iterator is UB, I think. – Steve Jessop Dec 17 '12 at 12:01
  • Are you sure that you must not overload std::begin? I ask because the standard library does so in a few cases itself. – ThreeBit Apr 01 '13 at 22:18
  • @ThreeBit: yes, I'm sure. The rules for standard library implementations are different from the rules for programs. – Steve Jessop Apr 01 '13 at 23:13
  • 3
    This needs to be updated for http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442. – T.C. Jul 16 '15 at 05:46
  • @T.C. what's the status of the proposed resolution? – Steve Jessop Jul 16 '15 at 13:40
  • It's adopted as a DR against C++11 back in April 2013. – T.C. Jul 16 '15 at 16:42
  • @T.C.: do you mean to say that it is not published in C++14? Or did I just ask the wrong question? I'm also interested in implementation status, although that might be harder to pin down. – Steve Jessop Jul 16 '15 at 17:25
  • @SteveJessop It's part of C++14, but also considered a DR against C++11 (i.e., should be implemented by compilers in their C++11 modes) – T.C. Jul 16 '15 at 17:43
  • Also, Thou Shalt Not Specialize std function templates since C++20. And specialization doesn't work for templates anyway. – L. F. Jul 23 '19 at 00:12
39

Should I just specialize begin() and end() ?

As far as I know, that is enough. You also have to make sure that incrementing the pointer would get from the begin to the end.

Next example (it is missing const version of begin and end) compiles and works fine.

#include <iostream>
#include <algorithm>

int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }
    int * begin()
    {
        return &v[0];
    }
    int * end()
    {
        return &v[10];
    }

    int v[10];
};

int main()
{
    A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

Here is another example with begin/end as functions. They have to be in the same namespace as the class, because of ADL :

#include <iostream>
#include <algorithm>


namespace foo{
int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }

    int v[10];
};

int *begin( A &v )
{
    return &v.v[0];
}
int *end( A &v )
{
    return &v.v[10];
}
} // namespace foo

int main()
{
    foo::A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • Thanks for your answer. But what if I don't have my own class but only my custom iterators ? `begin` and `end` should then be free functions but in which namespace ? – ereOn Nov 17 '11 at 09:32
  • 1
    @ereOn In the same namespace where the class is defined. See the 2nd example – BЈовић Nov 17 '11 at 09:43
  • Thank you for your answer :) And congratulations for your first 10K :) – ereOn Nov 17 '11 at 09:52
  • 2
    Congratulations as well :) It might be worth mentionning the terms Argument Dependent Lookup (ADL) or Koenig Lookup for the second example (to explain *why* the free function should be in the same namespace as the class it operates on). – Matthieu M. Nov 17 '11 at 10:11
  • @MatthieuM. Indeed: I'm not that familiar with ADL (reading up about it since I asked my question actually) and there is one thing I'm missing: It seems we don't need `using namespace foo` for the "range-based for loop" to work but we still need it when explicitely writing `begin(a)` ? Why is so ? – ereOn Nov 17 '11 at 10:14
  • 1
    @ereOn: actually, you don't. ADL is about extending the scopes to look-up to automatically include the namespaces that the arguments belong to. There is a good [ACCU article](http://accu.org/index.php/journals/268) about overload resolution, which unfortunately skips the name lookup part. The name lookup involves collecting candidates function, you start by looking in the current scope + the scopes of the arguments. If no name is found that match, you move up to the parent scope of the current scope and search again... until you reach the global scope. – Matthieu M. Nov 17 '11 at 10:23
  • @MatthieuM. Thank you very much for both the link and the explanation. Makes much more sense to me now. – ereOn Nov 17 '11 at 12:16
  • 1
    @BЈовић sorry, but for which reason in the end() function do you return a dangerous pointer? I know it works, but I want to understand the logic of this. The end of the array is v[9], why would you ever return v[10]? – gedamial Jun 26 '16 at 19:57
  • 1
    @gedamial I agree. I think it should be `return v + 10`. `&v[10]` dereferences the memory location just past the array. – Millie Smith Nov 06 '16 at 04:29
  • @gedamial Oh come on `v[10]` is the "past the end" position. The address is used in the comparison statement in the for range loop, it never gets deferenced. – Rick May 09 '20 at 12:36
  • @MillieSmith There is no difference between `v+10` and `&v[10]` – Rick May 09 '20 at 12:37
  • @Rick yes there is. One has defined behaviour, the other doesn't. `&v[10]` is defined to be equivalent to `&*(v+10)`, which does dereference the one-past-the-end pointer. – Caleth Aug 15 '22 at 14:41
  • @Caleth Thanks. You are right. `[` could be overloading for custom class. – Rick Aug 18 '22 at 06:03
15

In case you want to back a class's iteration directly with its std::vector or std::map member, here is the code for that:

#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;


/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////

class VectorValues {
private:
    vector<int> v = vector<int>(10);

public:
    vector<int>::iterator begin(){
        return v.begin();
    }
    vector<int>::iterator end(){
        return v.end();
    }
    vector<int>::const_iterator begin() const {
        return v.begin();
    }
    vector<int>::const_iterator end() const {
        return v.end();
    }
};

class MapValues {
private:
    map<string,int> v;

public:
    map<string,int>::iterator begin(){
        return v.begin();
    }
    map<string,int>::iterator end(){
        return v.end();
    }
    map<string,int>::const_iterator begin() const {
        return v.begin();
    }
    map<string,int>::const_iterator end() const {
        return v.end();
    }

    const int& operator[](string key) const {
        return v.at(key);
    }
    int& operator[](string key) {
        return v[key];
    } 
};


/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////

int main() {
    // VectorValues
    VectorValues items;
    int i = 0;
    for(int& item : items) {
        item = i;
        i++;
    }
    for(int& item : items)
        cout << item << " ";
    cout << endl << endl;

    // MapValues
    MapValues m;
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    for(auto pair: m)
        cout << pair.first << " " << pair.second << endl;
}
Chris Redford
  • 16,982
  • 21
  • 89
  • 109
  • 2
    It's worth mentioning that `const_iterator` can also be accessed in an `auto` (C++11)-compatible way via `cbegin`, `cend`, etc. – underscore_d Dec 19 '15 at 14:14
4

Inspired by BitTickler's comment about how to make it work for non-"container" types, here's a minimal example of something that works for doubles:

class dranged {
    double start, stop, step, cur;
    int index;

public:
    dranged(double start, double stop, double step) :
        start(start), stop(stop), step(step),
        cur(start), index(0) {}

    auto begin() { return *this; }
    auto end() { return *this; }

    double operator*() const { return cur; }

    auto& operator++() {
        index += 1;
        cur = start + step * index;
        return *this;
    }

    bool operator!=(const dranged &rhs) const {
        return cur < rhs.stop;
    }
};

Note that the use of < in the != operator maintains the correct invariant, but obviously assumes step is positive and wouldn't be appropriate everywhere a more general range would be. I've used an integer index to prevent propagation of floating point error, but have aimed for simplicity otherwise.

This can be used as:

double sum() {
    double accum = 0;
    for (auto val : dranged(0, 6.28, 0.1)) {
        accum += val;
    }
    return accum;
}

GCC and Clang both produce very reasonable code when compiled with optimisations (i.e. either -Os or above -O1 for GCC or -O2 for Clang).

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • struct over{ double minv; double delta; int steps; int i; over(double minv, double delta, double maxv):minv(minv), delta(delta), steps((maxv - minv)/delta),i(0){} over& begin() { return *this;} struct over_end{}; over_end end() { return over_end();} double operator*() const noexcept {return minv + i*delta;} bool operator!=([[maybe_unused]] over_end b){return i!=steps;} over& operator++(){ i++; return *this;} }; } for(double d:over(3,0.5,5)) – midjji Jun 08 '22 at 12:02
3

Here, I am sharing the simplest example of creating custom type, that will work with "range-based for loop":

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

Hope, it will be helpful for some novice developer like me :p :)
Thank You.

RajibTheKing
  • 1,234
  • 1
  • 15
  • 35
  • why not allocate one extra element to avoid dereferencing invalid memory in your end method? – AndersK Jun 25 '18 at 16:03
  • @Anders Because almost all end-iterators point to _after_ the end of their containing structure. The `end()` function itself obviously does not dereference an improper memory location, since it only takes the 'address-of' this memory location. Adding an extra element would mean you'd need more memory, and using `your_iterator::end()` in any way that would dereference that value would not work with any other iterators anyway because they are built the same way. – Qqwy Feb 12 '19 at 09:33
  • @Qqwy his end method de-refences - `return &data[sizeofarray]` IMHO it should just return the address data + sizeofarray but what do i know, – AndersK Feb 12 '19 at 12:09
  • @Anders You're correct. Thanks for keeping me sharp :-). Yes, `data + sizeofarray` would be the correct way to write this. – Qqwy Feb 12 '19 at 16:46
1

Chris Redford's answer also works for Qt containers (of course). Here is an adaption (notice I return a constBegin(), respectively constEnd() from the const_iterator methods):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};
user2366975
  • 4,350
  • 9
  • 47
  • 87
1

I think I don't have anything to explain since the answers already do that. But I maybe have to cite this quote from the standard (N4885):

[stmt.ranged]/1: (emphasise mine)

The range-based for statement

for ( init-statement(opt) for-range-declaration :
      for-range-initializer 
    ) statement(possibly curly-braced)

is equivalent to:

   { // starts namespace scope of for-range-initializer

       init-statement; (opt)
       auto &&range = for-range-initializer ;
       auto begin = begin-expr ;
       auto end = end-expr ;
       for ( ; begin != end; ++begin ) 
       {
          for-range-declaration = * begin ;
          statement ;   
       }

    } // ends namespace scope of for-range-initializer

where

(1.1) if the for-range-initializer is an expression, it is regarded as if it were surrounded by parentheses (so that a comma operator cannot be reinterpreted as delimiting two init-declarators);

(1.2) range, begin, and end are variables defined for exposition only; and

(3.1) begin-expr and end-expr are determined as follows:

(1.3.1) if the for-range-initializer is an expression of array type R, begin-expr and end-expr are range and range+N, respectively, where N is the array bound. If R is an array of unknown bound or an array of incomplete type, the program is ill-formed;

(1.3.2) if the for-range-initializer is an expression of class type C, and [class.member.lookup] in the scope of C for the names begin and end each find at least one declaration, begin-expr and end-expr are range.begin() and range.end(), respectively;

(1.3.3) otherwise, begin-expr and end-expr are begin(range) and end(range), respectively, where begin and end undergo argument-dependent lookup ([basic.lookup.argdep]).


Note that strings, arrays, and all STL containers are iterable data structures, so they can be iterated over with the range-based for-loop already. In order to make a data structure iterable, it must work similarly to the existing STL iterators:

1- There must be begin and end methods that operate on that structure, either as members or as stand-alone functions, and that return iterators to the beginning and end of the structure.

2- The iterator itself must support an operator*() method, an operator !=() method, and an operator++(void) method, either as members or as stand-alone functions.


#include <iostream>
#include <vector>
#define print(me) std::cout << me << std::endl

template <class T>
struct iterator
{
    iterator(T* ptr) : m_ptr(ptr) {};
    bool operator!=(const iterator& end) const { return (m_ptr != end.m_ptr); }
    T operator*() const { return *m_ptr; }
    const iterator& operator++()
    {
        ++m_ptr;
        return *this;
    }

private:
    T* m_ptr;
};

template <class T, size_t N>
struct array
{
    typedef iterator<T> iterator;

    array(std::initializer_list<T> lst)
    {

        m_ptr = new T[N]{};
        std::copy(lst.begin(), lst.end(), m_ptr);
    };

    iterator begin() const { return iterator(m_ptr); }
    iterator end() const { return iterator(m_ptr + N); }

    ~array() { delete[] m_ptr; }

private:
    T* m_ptr;
};

int main()
{
    array<std::vector<std::string>, 2> str_vec{ {"First", "Second"}, {"Third", "Fourth"} };
    for(auto&& ref : str_vec)
        for (size_t i{}; i != ref.size(); i++) 
            print(ref.at(i));

      //auto &&range = str_vec;
      //auto begin = range.begin();
      //auto end = range.end();
      //for (; begin != end; ++begin)
      //{
         // auto&& ref = *begin;
         // for (size_t i{}; i != ref.size(); i++) 
         //     print(ref.at(i));
      //}
}

The output of this program is:

First Second Third Fourth

mada
  • 1,646
  • 1
  • 15
0

I would like to elaborate some parts of @Steve Jessop's answer, for which at first I didn't understand. Hope it helps.

std::begin calls the begin() member function anyway, so if you only implement one of the above, then the results should be the same no matter which one you choose. That's the same results for ranged-based for loops, and also the same result for mere mortal code that doesn't have its own magical name resolution rules so just does using std::begin; followed by an unqualified call to begin(a).

If you implement the member functions and the ADL functions, though, then range-based for loops should call the member functions, whereas mere mortals will call the ADL functions. Best make sure they do the same thing in that case!


https://en.cppreference.com/w/cpp/language/range-for :

  • If ...
  • If range_expression is an expression of a class type C that has both a member named begin and a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin() and end_expr is __range.end();
  • Otherwise, begin_expr is begin(__range) and end_expr is end(__range), which are found via argument-dependent lookup (non-ADL lookup is not performed).

For range-based for loop, member functions are selected first.

But for

using std::begin;
begin(instance);

ADL functions are selected first.


Example:

#include <iostream>
#include <string>
using std::cout;
using std::endl;

namespace Foo{
    struct A{
        //member function version
        int* begin(){
            cout << "111";
            int* p = new int(3);  //leak I know, for simplicity
            return p;
        }
        int *end(){
            cout << "111";
            int* p = new int(4);
            return p;
        }
    };

    //ADL version

    int* begin(A a){
        cout << "222";
        int* p = new int(5);
        return p;
    }

    int* end(A a){
        cout << "222";
        int* p = new int(6);
        return p;
    }

}

int main(int argc, char *args[]){
//    Uncomment only one of two code sections below for each trial

//    Foo::A a;
//    using std::begin;
//    begin(a);  //ADL version are selected. If comment out ADL version, then member functions are called.


//      Foo::A a;
//      for(auto s: a){  //member functions are selected. If comment out member functions, then ADL are called.
//      }
}
Rick
  • 7,007
  • 2
  • 49
  • 79