2

Consider this code:

#include <iostream>

namespace A {
    struct Mine {};

    template <typename T1, typename T2>
    void foo(T1, T2)
    {
        std::cout << "A::foo" << std::endl;
    }
}

namespace B {
    template <typename T>
    void foo(T, T)
    {
        std::cout << "B::foo" << std::endl;
    }
}   

using namespace A;
using namespace B;
// or with the same effect:
//using A::foo;
//using B::foo;

int main()
{
    A::Mine a;
    foo(a, a);
}

The program prints B::foo instead of A::foo. Why does it use B::foo instead of A::foo?

Imagine the following situation: your library provides a namespace A and some other library provides namespace B which includes a function template with the same name foo as your library. Normally, there is no problem since the correct version can be selected using qualified calls or relying on the argument-dependent lookup. But if the user introduces both A::foo and B::foo to the same scope using a using declaration, the unqualified call is not ambiguous but the wrong function may be selected.

Is there a way to prefer A::foo over B::foo, since according to the argument-dependent lookup A::foo should be selected? What would be your advice in this situation?

Jakub Klinkovský
  • 1,248
  • 1
  • 12
  • 33
  • 3
    ADL just means namespace A would be searched even if you didn't have `using namespace A;` - it's not meant to (and doesn't) prioritorise `A` over a better match elsewhere. `template void foo(T, T)` is a better match - it's more restrictive / specialised. If you want to take the `B` version out of contention, you could use `enable_if`. – Tony Delroy Jul 11 '20 at 10:10
  • That's true, but what if you don't have control over what's in namespace `B`? E.g. it may be an external library. – Jakub Klinkovský Jul 11 '20 at 10:19
  • Do you have control of namespace A? You could add a `foo(Mine, Mine)`. Or add one in your own code that calls the `A::foo` implementation. Or explicitly call `A::foo(a, a)` at each point of use - though that's a bit ugly and may be impractical if you're relying on polymorphic dispatch. – Tony Delroy Jul 11 '20 at 10:22
  • Yes, namespace A is under my control. But adding `foo(Mine, Mine)` would not help much in the real world - there is not only `Mine`, but in fact many (templated) classes and `A::foo(T1, T2)` should cover all cases where both `T1` and `T2` are defined in namespace A. In the real case we also have a complicated `enable_if` to allow only such type combinations. – Jakub Klinkovský Jul 11 '20 at 10:31
  • Perhaps you could enable_if based on membership of the `A` namespace? It's an ugly hack, but see https://stackoverflow.com/questions/34974844/check-if-a-type-is-from-a-particular-namespace – Tony Delroy Jul 11 '20 at 10:43
  • Hmm, that's interesting... But still - if I add `template void foo(T, T)` into namespace A, with or without `enable_if`, it will be ambiguous with `B::foo` which does not have `enable_if`. Maybe we should just rename the function, although the real `foo` is the most common and obvious name for it and the problem might happen in the future with other libraries as well... Or should we just tell users not to use `using namespace` or `using`? – Jakub Klinkovský Jul 11 '20 at 11:09
  • Yeah - it's if they don't use the namespaces A and B, and you want to have forwarding `foo` functions that use `enable_if` to pick between forward-to-A and forward-to-B variants, that you might want that membership-of-namespace hack. From what you've described, I don't see a better approach based on what you've shown, but perhaps you could have member functions on `Mine` or something instead - hard for us to know what makes sense. – Tony Delroy Jul 11 '20 at 11:29

1 Answers1

4

It's actually doing the correct thing. Here's the reason.

namespace A {
    struct Mine {};

    template <typename T1, typename T2>
    void foo(T1, T2)
    {
        std::cout << "A::foo" << std::endl;
    }
}

The above code will be matched when there are 2 parameters passed of different (or same) type. Because you've defined both T1 and T2 differently (but they can also be same). But the function call is

foo(a, a);

having both the parameters of same type. Now a method foo with 2 parameters of same type is defined in the namespace B as below

namespace B {
    template <typename T>
    void foo(T, T)
    {
        std::cout << "B::foo" << std::endl;
    }
}

And thus the method from namespace B is matched, as both the signatures are different.

Try adding the below code

namespace C {
    template <typename T>
    void foo(T, T)
    {
        std::cout << "B::foo" << std::endl;
    }
}

and you'll end up with a compiler error, specifying there's ambiguity in the method definition and then you would have to manually resolve the scope of foo using something like A::foo(a,a) or whatever namespace you wish to use.

Debargha Roy
  • 2,320
  • 1
  • 15
  • 34
  • 1
    Yes, it's doing the correct thing according to the C++ standards, but not according to the programmer's intention :-) – Jakub Klinkovský Jul 11 '20 at 10:24
  • @JakubKlinkovský Fair enough, but programmer's intention should be based on what the language specifies. That can sometimes be hard with c++ though :) – cigien Jul 11 '20 at 16:48
  • Thus should we say, the art of expressing our intentions to computer is called Programming! :P – Debargha Roy Jul 12 '20 at 12:20