Question

I found this interesting bit in Boost.Range:

When providing free-standing functions range_begin/end(), the docs state that:

... range_begin() and range_end() must be overloaded for both const and mutable reference arguments.

And indeed, looking at their defaults in end.hpp, we see:

    //////////////////////////////////////////////////////////////////////
    // pair
    //////////////////////////////////////////////////////////////////////

    template< typename Iterator >
    inline Iterator range_end( const std::pair<Iterator,Iterator>& p )
    {
        return p.second;
    }

    template< typename Iterator >
    inline Iterator range_end( std::pair<Iterator,Iterator>& p )
    {
        return p.second;
    }

You will note (and the example given in the docs also does this) that both versions return the same Iterator type.

Why do we need both overload in the first place? Is it to make ADL work?

Was it helpful?

Solution

You obviously need the const & version because otherwise your range_begin would be uncallable for const-qualified objects.

Less obvious is why you also need the & version, but it's simple: if you don't provide it, then your custom function is a worse match than Boost's own version.

Here's a short non-Boost example:

namespace M {
  struct S { };
  void f(const S &);
}

namespace N {
  template <typename T>
  void f(T &);

  template <typename T>
  void g(T &t) { f(t); }
}

void h() {
  M::S s {};
  N::g(s);
}

Here, during instantiation of N::g<M::S>, an unqualified call f(t) is made, and the argument t has type M::S. There are two candidates: N::f<M::S> is in the same namespace, but ADL also finds M::f. The former's parameter is M::S &. The latter's is const M::S &. That means the former is a better match, even if you really want the version in namespace M to be used.

An additional overload M::f(S &) avoids this problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top