c++argument-dependent-lookup

argument dependent lookup preference during unqualified lookup


I'm trying to understand ADL based lookup. The following snippet results in a compilation error due to ambiguous overloads (error below). Shouldn't the version found by ADL given preference and selected instead?

template <typename It, typename F>
void for_each(It begin, It end, F f) {}

int main() {
    std::vector<int> vec;
    for_each(vec.begin(), vec.end(), [](int &x){});  
}
source>: In function 'int main()':
<source>:70:13: error: call of overloaded 'for_each(std::vector<int>::iterator, std::vector<int>::iterator, main()::<lambda(int&)>)' is ambiguous
   70 |     for_each(vec.begin(), vec.end(), [](int &x){});
      |     ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:64:6: note: candidate: 'void for_each(It, It, F) [with It = __gnu_cxx::__normal_iterator<int*, std::vector<int> >; F = main()::<lambda(int&)>]'
   64 | void for_each(It begin, It end, F f) {
      |      ^~~~~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20230422/include/c++/14.0.0/algorithm:61,
                 from <source>:3:
/opt/compiler-explorer/gcc-trunk-20230422/include/c++/14.0.0/bits/stl_algo.h:3827:5: note: candidate: '_Funct std::for_each(_IIter, _IIter, _Funct) [with _IIter = __gnu_cxx::__normal_iterator<int*, vector<int> >; _Funct = main()::<lambda(int&)>]'
 3827 |     for_each(_InputIterator __first, _InputIterator __last, _Function __f)

Solution

  • Shouldn't the version found by ADL given preference and selected instead?

    No, that's not the function of ADL. The purpose in examining related namespaces is to find candidates that would not otherwise be found (but are arguably a natural part of the API for the types in the namespace).

    Those candidates are not preferred, but instead just go into the overload resolution process along with any candidates found by regular lookup rules, and here we can have conflicts. Since both overloads of for_each we compare are generated from templates, viable, unconstrained, and don't rank higher than each other in partial ordering of function templates, the call is ambiguous.

    This may seem counter-intuitive when looking at such a simple example, but halting compilation and letting programmers resolve conflicts is really the only safe option. C++ is incredibly complex, between the various ways a name can be brought into scope, and the fact multiple related namespaces can be examined during ADL, it'd be bad if the language chose another overload silently when the code around a call changed in some invisible way.