c++iteratorargument-dependent-lookup

ADL not working as expected


struct S
{
    vector<int> v;
    void method()
    {
        begin(v);
    }
};

The above code snippet compiles fine, because of ADL until I add

auto begin() { return begin(v); }

to the class declaration. At that point C++ forgets about ADL and instead prefers S::begin that doesn't even have a viable overload, producing the error

error: no matching function for call to ‘S::begin(std::vector<int>&)’ begin(v);

Is there any fix to this? I am asking, because after reading Why use non-member begin and end functions in C++11?, I started using begin() and end() free functions everywhere for consistency, but now I am getting conflicts after defining my own begin() and end() methods.


Solution

  • You're using begin as an unqualified name, and unqualified name lookup

    ... examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined.

    reference.

    From your member function's perspective, the first scope providing a name begin is the class scope, so it populates the overload set from that scope, and stops looking.

    Only after this name lookup stage, does it try to choose one of the overload set, and decide nothing matches. The compiler doesn't then go back and start searching from the next scope, it just gives up.

    Your options are:

    1. use the existing container member function instead of the free function (this is slightly less verbose than the explicitly-qualified version below)

      auto begin() { return v.begin(); }
      
    2. use qualified names instead

      auto begin() { return ::std::begin(v); }
      
    3. add the correct overload to class scope, with using std::begin;

      Ignore that one, I forgot you can't introduce non-member names at class scope with using.

    4. inject the correct overload into the narrower scope of the member function body itself, so the search stops there

      auto begin() { using std::begin; return begin(v); }
      
    5. stop providing a begin member function in the first place, and instead add a non-member begin overload to the enclosing namespace. This is more modern and avoids the lookup problem.

      namespace N {
        struct S { std::vector<int> v; };
        std::vector<int>::iterator begin(S& s) { return s.v.begin(); }
        // and end, cbegin, etc. etc.
      }