views:

70

answers:

4

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?

+1  A: 

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.

Sebastian
+2  A: 

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() );
Johannes Schaub - litb
The operator argument that Charles made as well is convincing of course. It is not really surprising that base class namespaces are considered for ADL. In my real world example it is the template argument namespace that gets included. Very surprising, at least until I read the standard :-)
Sebastian
+3  A: 

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.

Charles Bailey
Thanks, you're right. I had considered ADL to be the exception rather than the rule, used to find strictly better matches maybe. So with your argument, the compiler has to go looking in other namespaces to find a better match, but if he goes looking and finds an indistinguishable match, failing with an error is the sensible thing to do.
Sebastian
A: 

I think you stated the problem yourself:

in the global namespace

Functions in the global namespace are considered last. It's the most outer scope by definition. Any function with the same name (not necessarily applicable) that is found in a closer scope (from the call point of view) will be picked up first.

template <typename Rng, typename T>
typename Rng::iterator find( Rng& rng, T const& t );

namespace foo
{
  bool find(std::vector<int> const& v, int);

  void method()
  {
    std::deque<std::string> deque;
    auto it = find(deque, "bar");
  }
}

Here (unless vector or deque include algorithm, which is allowed), the only method that will be picked up during name look-up will be:

bool foo::find(std::vector<int> const&, int);

If algorithm is somehow included, there will also be:

template <typename FwdIt>
FwdIt std::find(FwdIt begin, FwdIt end,
                typename std::iterator_traits<FwdIt>::value_type const& value);

And of course, overload resolution will fail stating that there is no match.

Note that name-lookup is extremely dumb: neither arity nor argument type are considered!

Therefore, there are only two kinds of free-functions that you should use in C++:

  • Those which are part of the interface of a class, declared in the same namespace, picked up by ADL
  • Those which are not, and that you should explicitly qualified to avoid issues of this type

If you fall out of these rules, it might work, or not, depending on what's included, and that's very awkward.

Matthieu M.
My example is slightly different from yours. In my example, ADL finds an implementation in a namespace the caller is not part of at all.
Sebastian
@Sebastian: "caller" is ambiguous. There are two things to consider: the point of call itself, and the arguments. My example demonstrated both, even though I didn't made it more complicated by using inheritance.
Matthieu M.