keith
keith

Reputation: 5342

C++ name resolution woes

I am have a custom class that I want to provide a templated version of the sin function to - I've called it foo in my minimum verifiable reporduction. Due to some template meta programming elsewhere, it's advantageous for my sin function to have the same name as the normal sin function as it is doing the same job, just on different types.

However, the following will not compile:

#include <cmath>
#include <iostream>
#include <cstdlib>

namespace test
{
    template <std::size_t size>
    class foo
    {

    };

    template <std::size_t size>
    void sin(const foo<size>& f)
    {
    }

    template <class T>
    void bar(const T& x)
    {
        using namespace ::std;

        sin(x);
    }
}

int main()
{
    test::bar(0.5);

    std::cout << "Done...";

    std::getchar();

    return EXIT_SUCCESS;
}

I get the following compiler error:

..\test\problem.cpp(23,3): error: no matching function for call to 'sin'
                sin(x);
                ^~~
..\test\problem.cpp(29,8): note: in instantiation of function template specialization 'test::bar<double>' requested here
        test::bar(0.5);
              ^
..\test\problem.cpp(14,7): note: candidate template ignored: could not match 'foo<size>' against 'const double'
        void sin(const foo<size>& f)
             ^
1 error generated.

I am surprised by this. Is there a way to get bar to compile without using template meta programming hacks to create several versions of bar?

Fails in Clang 9 and MSVC 2019.

Upvotes: 2

Views: 129

Answers (2)

Michael Kenzel
Michael Kenzel

Reputation: 15941

There is a difference between a using directive and a using declaration. using namespace ::std; is a using directive. As far as unqualified name lookup is concerned, a using directive basically just forwards name lookup to also search namespace ::std if a name was not found at the point name lookup reaches the nearest namespace that encloses both, the namespace that contained the directive and the namespace nominated by the directive [namespace.udir]/2. In your case, this means name lookup would have to reach the global namespace before it could find ::std::sin. However, name lookup will reach the namespace test first, which contains a function named sin, and then just stop because it has found what it was looking for [basic.lookup.unqual]/1. What you want is a using declaration:

    template <class T>
    void bar(const T& x)
    {
        using std::sin;

        sin(x);
    }

Contrary to a using directive, a using declaration directly introduces the declared name into the scope in which the using declaration resides [namespace.udecl]/1, which means std::sin will be found right away. Now, this means normal unqualified name lookup will not find test::sin from within the block scope after the using declaration anymore. However, thanks to argument-dependent name lookup [basic.lookup.argdep], test::sin will be found and part of the overload set again in case T is an instance of test::foo

Upvotes: 3

Adrian Mole
Adrian Mole

Reputation: 51845

Here's a 'quick fix' based on (a) the comments and (b) the answer linked in the comments. A minimal version of your bar function would be:

    void bar()
    {
        ::sin(0.5); // Without "::" your templated "test::sin" hides the global namespace
                    // version - which is here referenced EXPLICITLY.
    }

Upvotes: 2

Related Questions