Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Name resolution of functions inside templates instantiated with qualified types

Consider the following C++ code example:

namespace n
{
    struct A {};
}

struct B {};

void foo(int) {}

template<typename T>
void quux()
{
    foo(T());
}

void foo(n::A) {}
void foo(B) {}


int main()
{
    quux<n::A>(); // Error (but works if you comment out the foo(int) declaration)
    quux<B>();    // Works

    return 0;
}

As indicated in the comment, the template instantiation quux<n::A>() causes a compiler error (on GCC 4.6.3):

foo.cpp: In function ‘void quux() [with T = n::A]’:
foo.cpp:22:16:   instantiated from here
foo.cpp:13:5: error: cannot convert ‘n::A’ to ‘int’ for argument ‘1’ to ‘void foo(int)’

Can someone explain to me what is going on? I would have expected for it to work the same as with quux<B>(). It must have something to do with when foo is considered dependent. Unfortunately my C++ foo is not good enough. The example compiles fine, when the foo(int) declaration is not present, which is also surprising to me.

Any hints, explanations and workarounds are welcome.

Update 1:

I do not want to (read cannot) move the declaration of foo(n::A) before the definition of quux (which would avoid the error).

Update 2:

Thanks for David for pointing out the related question Template function call confused by function with wrong signature declared before template. The accepted answer by Johannes Schaub - litb proposes a wrapper class solution, that would also work in my case as a workaround. However, I'm not 100% happy with it.

Update 3:

I solved the issue by putting the definition of foo(n::A) in namespace n. Thanks for the helpful answers of Jesse Good and bames53 that not only point out the relevant sections of the standard, but also provide alternative solutions. Thanks to David Rodríguez - dribeas for his explanation when I did not understand the proposed solutions properly and all other contributors.

like image 889
Nikolaus Demmel Avatar asked Nov 09 '12 22:11

Nikolaus Demmel


2 Answers

I think the rule is 14.6.4.2p1:

For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:

— For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.

— For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

void foo(n::A) {} is not visible in the template definition context because it comes after and foo is not in the same namespace as n::A. So it needs to be either visible before the template definition or included in the same namespace like below:

namespace n
{
    void foo(n::A) {}
}
like image 118
Jesse Good Avatar answered Nov 14 '22 14:11

Jesse Good


The error my compiler gives is:

main.cpp:11:5: error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup
    foo(T());
    ^
main.cpp:18:5: note: in instantiation of function template specialization 'quux<n::A>' requested here
    quux<n::A>(); // Error (but works if you comment out the foo(int) declaration)
    ^
main.cpp:14:6: note: 'foo' should be declared prior to the call site or in namespace 'n'
void foo(n::A) {}
     ^

Which makes it clear what the problem is.

Commenting out void foo(int) does not make it work however; that's just a bug/extension in your compiler.

You mention that you can't define void foo(n::A) before quux(), however when you say in the comments that you can't define it inside namespace n, the reasons you give don't seem to apply. This should fix the problem without the other problems you mention.

template<typename T>
void quux()
{
    foo(T());
}

namespace n {
    void foo(n::A) {}
}
using n::foo; // in case there's any other code that depends on getting foo(n::A) from the global namespace

void foo(B) {} // this stays in the global namespace

If you can't move the definition of void foo(n::A) to where it works with proper two-phase lookup (again, either before quux() or inside namespace n) there's a sort of hacky solution which may work for you: forward declare the proper overload of foo() inside quux().

template<typename T>
void quux()
{
    void foo(T);
    foo(T());
}

The function eventually must be defined inside the same namespace as quux() and it has to match the 'generic' forward declaration.


Or there's another alternative. It's only been fairly recent that most C++ compilers started offering correct two-phase name lookup, so there's a lot of code out there that isn't correct but which compilers want to support. If you can't change your code then it may be a good candidate for enabling a compatibility compiler option; my compiler takes the flag -fdelayed-template-parsing to disable two-phase name lookup and instead always look names up in the instantiation context.

like image 33
bames53 Avatar answered Nov 14 '22 13:11

bames53