Search code examples
c++templatesnamespacesname-lookup

What is the difference between qualified and unqualified name lookup when deducting templates?


While writing template methods, i ran into the folowing behavior, which i do not understand.

I have the folowing code:

#include <array>
#include <iostream>

namespace A
{
    template<typename T>
    class Bar
    {
            T Var;
    };
}

namespace B
{

    template<typename T>
    void Foo(const T& var)
    {
            std::cout << "default template method has been called" << std::endl << std::endl;
    }

    template<typename T, size_t N>
    void Foo(const std::array<T,N>& var)
    {
            std::cout << "Array overload has been called" << std::endl;
            for(auto& elem : var)
            {
                Foo(elem);
            }
    }

    template<typename T>
    void Foo(const A::Bar<T>& var)
    {
            std::cout << "Bar overload has been called" << std::endl << std::endl;
    }
}

int main()
{
    int                        VarInt;
    A::Bar<int>                VarBar;
    std::array<int, 1>         ArrayInt;
    std::array<A::Bar<int>, 1> ArrayBar;

    B::Foo(VarInt);
    B::Foo(VarBar);
    B::Foo(ArrayInt);
    B::Foo(ArrayBar);
    return 0;
}

It's output is not what i expect as with the array of Bar, the default template is called instead of the Bar overload:

default template method has been called

Bar overload has been called

Array overload has been called
default template method has been called

Array overload has been called
default template method has been called

I noticed that doing the folowing enables the compiler to find the correct overloads :

  • removing all namespaces or
  • declaring all the templates prototypes before implementing them

Solution

  • It's output is not what i expect as with the array of Bar, the default template is called instead of the Bar overload

    For the call expression, B::Foo(ArrayBar), the 2nd overload void Foo(const std::array<T,N>& var) is chosen with N deduced to 1 and T deduced to A::Bar<int>.

    Now inside that overload, when the call expression Foo(elem) is encountered, the compiler doesn't know about the third overloaded Foo. Thus it chooses the first overload of Foo which is the most general among the two available at that point.


    declaring all the templates prototypes before implementing them

    Providing the declarations for all the overloads of Foo before implementing them, lets the compiler know that there is a third overload of Foo. So this time, the call expression Foo(elem) calls the third overloaded Foo as expected(because it is more special than the first overload of Foo).


    removing all namespaces

    And when everything is put in the same global namespace, due to ADL the third overload is also found.