Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous call to templated function due to ADL

I've been bitten by this problem a couple of times and so have my colleagues. When compiling

#include <deque>
#include <boost/algorithm/string/find.hpp>
#include <boost/operators.hpp>

template< class Rng, class T >    
typename boost::range_iterator<Rng>::type find( Rng& rng, T const& t ) {
      return std::find( boost::begin(rng), boost::end(rng), t );
}

struct STest {
      bool operator==(STest const& test) const { return true; }
};

struct STest2 : boost::equality_comparable<STest2>   {
      bool operator==(STest2 const& test) const { return true; }
};

void main() {
      std::deque<STest> deq;
      find( deq, STest() ); // works
      find( deq, STest2() ); // C2668: 'find' : ambiguous call to overloaded function
}

...the VS9 compiler fails when compiling the second find. This is due to the fact that STest2 inherits from a type that is defined in boost namespace which triggers the compiler to try ADL which finds boost::algorithm::find(RangeT& Input, const FinderT& Finder).

An obvious solution is to prefix the call to find(…) with "::" but why is this necessary? There is a perfectly valid match in the global namespace, so why invoke Argument-Dependent Lookup? Can anybody explain the rationale here?

like image 475
Sebastian Avatar asked Sep 09 '10 10:09

Sebastian


3 Answers

ADL isn't a fallback mechanism to use when "normal" overload resolution fails, functions found by ADL are just as viable as functions found by normal lookup.

If ADL was a fallback solution then you might easily fall into the trap were a function was used even when there was another function that was a better match but only visible via ADL. This would seem especially strange in the case of (for example) operator overloads. You wouldn't want two objects to be compared via an operator== for types that they could be implicitly converted to when there exists a perfectly good operator== in the appropriate namespace.

like image 87
CB Bailey Avatar answered Sep 19 '22 02:09

CB Bailey


I'll add the obvious answer myself because I just did some research on this problem:

C++03 3.4.2

§2 For each argument type T in the function call, there is a set of zero or more associated namespaces [...] The sets of namespaces and classes are determined in the following way:

[...]

— If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces in which its associated classes are defined.

§ 2a If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered. Otherwise the set of declarations found by the lookup of the function name is the union of the set of declarations found using ordinary unqualified lookup and the set of declarations found in the namespaces and classes associated with the argument types.

At least it's standard conformant, but I still don't understand the rationale here.

like image 35
Sebastian Avatar answered Sep 21 '22 02:09

Sebastian


Consider a mystream which inherits from std::ostream. You would like that your type would support all the << operators that are defined for std::ostream normally in the std namespace. So base classes are associated classes for ADL.

I think this also follows from the substitution principle - and functions in a class' namespace are considered part of its interface (see Herb Sutter's "What's in a class?"). So an interface that works on the base class should remain working on a derived class.

You can also work around this by disabling ADL:

(find)( deq, STest2() );
like image 35
Johannes Schaub - litb Avatar answered Sep 19 '22 02:09

Johannes Schaub - litb