Begemoth
Begemoth

Reputation: 1389

ADL and container functions (begin, end, etc)

C++11 and later define free functions begin, end, empty, etc in namespace std. For most containers these functions invoke the corresponding member function. But for some containers (like valarray) these free functions are overloaded (initializer_list does not have a member begin()). So to iterate over any container free functions should be used and to find functions for container from namespaces other than std ADL should be used:

 template<typename C>
 void foo(C c)
 {
   using std::begin;
   using std::end;
   using std::empty;

   if (empty(c)) throw empty_container();
   for (auto i = begin(c); i != end(c); ++i) { /* do something */ }
 }

Question 1: Am I correct? Are begin and end expected to be found via ADL?

But ADL rules specify that if type of an argument is a class template specialization ADL includes namespaces of all template arguments. And then Boost.Range library comes into play, it defines boost::begin, boost::end, etc. These functions are defined like this:

template< class T >
inline BOOST_DEDUCED_TYPENAME range_iterator<T>::type begin( T& r )
{
    return range_begin( r );
}

If I use std::vector<boost::any> and a Boost.Range I run into trouble. std::begin and boost::begin overloads are ambiguous. That it, I can not write template code that will find a free begin via ADL. If I explicitly use std::begin I expect that any non-std:: container has a member begin.

Question 2: What shall I do in this case?

Rely on the presence of member function? Simplest way.

Ban Boost.Range? Well, algorithms that take container instead of a pair of iterators are convinient. Boost.Range adaptors (containers that lazily apply an algorithm to a container) are also convinient. But if I do not use Boost.Range in my code it still can be used in a boost library (other than Range). This make template code really fragile.

Ban Boost?

Upvotes: 7

Views: 472

Answers (1)

JDługosz
JDługosz

Reputation: 5642

A few years ago, I had a similar problem where my code suddenly started getting ambiguities between std::begin and boost::begin. I found it was due to using Boost.Operator to aid in defining a class, even though it was not even a public base class or apparent to the user of the types involved. A random change somewhere caused #include <boost/range/begin.hpp> to be present in the nested include files somewhere, thus making boost::begin visible to the compiler.

I complained to the mailing list about the putting of classes directly in the Boost namespace, rather than in a nested class and exposing them via using declarations; all stuff defined directly in the Boost namespace could potentially step on each other via accidental ADL.

I just tried to reproduce this today, and it seems quite resilient against such ambiguities now! Looking at the definition, boost::begin is itself in an inner namespace so it can never be found via unqualified lookup if you had not supplied your own using boost::begin; in your own scope.

I don’t know how long ago this fix took place. (If you can still reproduce it, please post a complete program with version and platform details.)

So your answer is:

For Boost, don’t worry about it anymore (upgrade Boost if necessary).

For new code, never define a free function named begin in the same namespace as any of its defined types.

Upvotes: 0

Related Questions