Search code examples
c++boostnamespacesargument-dependent-lookupboost-range

Why is ADL not working with Boost.Range?


Considering:

#include <cassert>
#include <boost/range/irange.hpp>
#include <boost/range/algorithm.hpp>

int main() {
    auto range = boost::irange(1, 4);
    assert(boost::find(range, 4) == end(range));
}

Live Clang demo Live GCC demo

this gives:

main.cpp:8:37: error: use of undeclared identifier 'end'

Considering that if you write using boost::end; it works just fine, which implies that boost::end is visible:

Why is ADL not working and finding boost::end in the expression end(range)? And if it's intentional, what's the rationale behind it?


To be clear, the expected result would be similar to what happens in this example using std::find_if and unqualified end(vec).


Solution

  • In boost/range/end.hpp they explicitly block ADL by putting end in a range_adl_barrier namespace, then using namespace range_adl_barrier; to bring it into the boost namespace.

    As end is not actually from ::boost, but rather from ::boost::range_adl_barrier, it is not found by ADL.

    Their reasoning is described in boost/range/begin.hpp:

    // Use a ADL namespace barrier to avoid ambiguity with other unqualified
    // calls. This is particularly important with C++0x encouraging
    // unqualified calls to begin/end.

    no examples are given of where this causes a problem, so I can only theorize what they are talking about.

    Here is an example I have invented of how ADL can cause ambiguity:

    namespace foo {
      template<class T>
      void begin(T const&) {}
    }
    
    namespace bar {
      template<class T>
      void begin(T const&) {}
    
      struct bar_type {};
    }
    
    int main() {
      using foo::begin;
      begin( bar::bar_type{} );
    }
    

    live example. Both foo::begin and bar::begin are equally valid functions to call for the begin( bar::bar_type{} ) in that context.

    This could be what they are talking about. Their boost::begin and std::begin might be equally valid in a context where you have using std::begin on a type from boost. By putting it in a sub-namespace of boost, std::begin gets called (and works on ranges, naturally).

    If the begin in the namespace boost had been less generic, it would be preferred, but that isn't how they wrote it.