stl is not - Why use non-member begin and end functions in C++11?





3 Answers

Consider the case when you have library that contain class:

class SpecialArray;

it has 2 methods:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

to iterate over it's values you need to inherit from this class and define begin() and end() methods for cases when

auto i = v.begin();
auto e = v.end();

But if you always use

auto i = begin(v);
auto e = end(v);

you can do this:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

where SpecialArrayIterator is something like:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

now i and e can be legally used for iteration and accessing of values of SpecialArray

of std begin()

Every standard container has a begin and end method for returning iterators for that container. However, C++11 has apparently introduced free functions called std::begin and std::end which call the begin and end member functions. So, instead of writing

auto i = v.begin();
auto e = v.end();

you'd write

using std::begin;
using std::end;
auto i = begin(v);
auto e = end(v);

In his talk, Writing Modern C++, Herb Sutter says that you should always use the free functions now when you want the begin or end iterator for a container. However, he does not go into detail as to why you would want to. Looking at the code, it saves you all of one character. So, as far as the standard containers go, the free functions seem to be completely useless. Herb Sutter indicated that there were benefits for non-standard containers, but again, he didn't go into detail.

So, the question is what exactly do the free function versions of std::begin and std::end do beyond calling their corresponding member function versions, and why would you want to use them?




To answer your question, the free functions begin() and end() by default do nothing more than call the container's member .begin() and .end() functions. From <iterator>, included automatically when you use any of the standard containers like <vector>, <list>, etc., you get:

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

The second part of you question is why prefer the free functions if all they do is call the member functions anyway. That really depends on what kind of object v is in your example code. If the type of v is a standard container type, like vector<T> v; then it doesn't matter if you use the free or member functions, they do the same thing. If your object v is more generic, like in the following code:

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

Then using the member functions breaks your code for T = C arrays, C strings, enums, etc. By using the non-member functions, you advertise a more generic interface that people can easily extend. By using the free function interface:

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

The code now works with T = C arrays and C strings. Now writing a small amount of adapter code:

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

We can get your code to be compatible with iterable enums too. I think Herb's main point is that using the free functions is just as easy as using the member functions, and it gives your code backward compatibility with C sequence types and forward compatibility with non-stl sequence types (and future-stl types!), with low cost to other developers.




One benefit of std::begin and std::end is that they serve as extension points for implementing standard interface for external classes.

If you'd like to use CustomContainer class with range-based for loop or template function which expects .begin() and .end() methods, you'd obviously have to implement those methods.

If the class does provide those methods, that's not a problem. When it doesn't, you'd have to modify it*.

This is not always feasible, for example when using external library, esspecially commercial and closed source one.

In such situations, std::begin and std::end come in handy, since one can provide iterator API without modifying the class itself, but rather overloading free functions.

Example: suppose that you'd like to implement count_if function that takes a container instead of a pair of iterators. Such code might look like this:

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

Now, for any class you'd like to use with this custom count_if, you only have to add two free functions, instead of modifying those classes.

Now, C++ has a mechanisim called Argument Dependent Lookup (ADL), which makes such approach even more flexible.

In short, ADL means, that when a compiler resolves an unqualified function (i. e. function without namespace, like begin instead of std::begin), it will also consider functions declared in namespaces of its arguments. For example:

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

In this case, it doesn't matter that qualified names are some_lib::begin and some_lib::end - since CustomContainer is in some_lib:: too, compiler will use those overloads in count_if.

That's also the reason for having using std::begin; and using std::end; in count_if. This allows us to use unqualified begin and end, therefore allowing for ADL and allowing compiler to pick std::begin and std::end when no other alternatives are found.

We can eat the cookie and have the cookie - i. e. have a way to provide custom implementation of begin/end while the compiler can fall back to standard ones.

Some notes:

  • For the same reason, there are other similar functions: std::rbegin/rend, std::size and std::data.

  • As other answers mentions, std:: versions have overloads for naked arrays. That's useful, but is simply a special case of what I've described above.

  • Using std::begin and friends is particularly good idea when writing template code, because this makes those templates more generic. For non-template you might just as well use methods, when applicable.

P. S. I'm aware that this post is nearly 7 years old. I came across it because I wanted to answer a question which was marked as a duplicate and discovered that no answer here mentions ADL.






Related