Name resolution of functions inside templates instantiated with qualified types
Asked Answered
H

2

7

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.

Hexateuch answered 9/11, 2012 at 22:6 Comment(14)
Move the quux body to a later position in the code, after all the names foo have been declared.Simpleton
Yes, that solves the error. However in my real program that is not possible/desired.Hexateuch
@KerrekSB: That might or not be an option. Clang++ yields a more informative message: error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup. Now you will need to find where the lookup for that particular expression is defined in the standard :) --possible workaround: move foo(n::A) to the n namespaceShelli
possible duplicate of Template function call confused by function with wrong signature declared before templateDeter
you cannot even have forward declarations of foo before the definition of quux?Vest
@didirec: No. Think of quux() as write(std::vector<T>) and foo(n::A) as write(n::A). I want the vector version in a common header file, to use in the print definitions of other print functions all around the place.Hexateuch
What about move the body of quux() after your main() function and keep its signature in the same place?Putrid
@David: Not sure whether it is a duplicate yet. But definitely strongly related. It seems I do not quite understand dependent name lookup right. I'm trying to read up in the standard sections pointed out in that related post and Jesse Good's answer below.Hexateuch
@VictorHugo: Not a viable solution, thanks. Think of quux as being defined in a header file.Hexateuch
@DavidRodríguez-dribeas: Thanks, I need to find out exactly how argument dependent lookup works. Moving foo into the namespace n is not an option. Moreover, it does not solve the problem, but rather results in quux<B> not working either and also removing the foo(int) will then also not solve the issue but rather result in an error and template definition (because now the n::foo name is not dependent, because it is a qualified name).Hexateuch
@NikolausDemmel: I am not sure you understood the proposed change: move foo(n::A) into the n namespace (leave foo(B) where it is, that is in the same namespace as B). Do not make a qualified call to foo (i.e. leave foo(T()), do not make it n::foo(T())). That should fix it (assuming that you can move foo(n::A) into namespace n)Shelli
@DavidRodríguez-dribeas: Aha, you are right. I misinterpreted your proposal. In fact this is very workable in my case and solves the problem. Thank you! If you want to add it as an answer, I will accept that answer. Otherwise I will accept Jesse Good's answer (as he explains why it does not work) and point out your solution in an update to my question.Hexateuch
@NikolausDemmel: Jesse's answer actually explains what the issue is and also hints at the solution with the snippet where void foo(A) is moved to the n namespace. You should accept his answer.Shelli
@DavidRodríguez-dribeas: You are right again. I'm too tired. Anyway, thanks everyone.Hexateuch
A
2

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) {}
}
Alded answered 9/11, 2012 at 22:17 Comment(5)
Thanks for the hint and references to the standard. I think I'm getting close, however I still don't quite understand. The fact the quux<B>() works is because foo is in the same namespace as B, thus foo is found in the associated namespace (global in the example) during lookup for instantiation context? quux<A>() does not work, because foo is not found in the associated namespace (which is only n)? Why does it work if foo(int) is commented out?Hexateuch
I just found a solution. If I call ::quux<n::A>(T), then it works. My explanation would be that the explicit qualification makes the global namespace an associated namespace. Does that make sense? Is there a solution that could be part of the definition of quux, not the use of quux? Update: That was bogus, it doesn't actually work.Hexateuch
On top of the question of why it works if foo(int) is commented out, I'd like to find out if there is a way/workaround that adds the namespace of the definition of quux (i.e. ::) or the point of instantiation of quux<n::A> (also ::) to the associated namespaces that are search in instantiation context?Hexateuch
And one last thing. This associated namespace lookup (3.4.2) is the Koenig lookup, right?Hexateuch
It shouldn't work if foo(int) is commented out (see here). Another name for argument dependent lookup (ADL) is Koenig lookup after the person who proposed it to the C++ standards committee.Alded
W
1

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.

Wahlstrom answered 12/11, 2012 at 15:35 Comment(2)
Thanks for the very detailed answer. Your first solution is actually what I have now implemented (thanks to David Rodríguez - dribeas and Jesse Good). When I said I could not put foo(n::A) in the n namespace I did not understand the solution yet. Thanks also for the two alternative solutions, despite me not needing them in this instance.Hexateuch
What compiler are you using? The error message is clearly a lot superior to gcc.Hexateuch

© 2022 - 2024 — McMap. All rights reserved.