Declare function after template defined
Asked Answered
P

1

3

Let's say I have a template function:

template <class T>
void tfoo( T t )
{
    foo( t );
}

later I want to use it with a type, so I declare/define a function and try to call it:

void foo( int );

int main()
{
    tfoo(1);
}

and I am getting error from g++:

‘foo’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive] foo( t );

why it cannot find void foo(int) at the point of instantiation? It is declared at that point. Is there a way to make it work (without moving declaration of foo before template)?

Penelopepeneplain answered 29/11, 2016 at 20:45 Comment(4)
isn't it because the two phase lookup?Minni
@Minni actually I would expect this to compile because of two phase lookup.Penelopepeneplain
Perhaps declaring the function you want to call inside the function template will solve the problem for you?Haemorrhage
Following my conversation with AnT and W.F. in the comments, what about a wrapper with a conversion operator that allows the use of ADL to call foo? Like this: coliru.stacked-crooked.com/a/dc92e572d02f601aPersis
C
10

foo in your case is a dependent name, since function choice depends on the type if the argument and the argument type depends on the template parameter. This means that foo is looked up in accordance with the rules of dependent lookup.

The difference between dependent and non-dependent lookup is that in case of dependent lookup ADL-nominated namespaces are seen as extended: they are extended with extra names visible from the point of template instantiation (tfoo call in your case). That includes the names, which appeared after the template declaration. The key point here is that only ADL-nominated namespaces are extended in this way.

(By ADL-nominated namespace I refer to namespace associated with function argument type and therefore brought into consideration by the rules of dependent name lookup. See "3.4.2 Argument-dependent name lookup")

In your case the argument has type int. int is a fundamental type. Fundamental types do not have associated namespaces (see "3.4.2 Argument-dependent name lookup"), which means that it does not nominate any namespace through ADL. In your example ADL is not involved at all. Dependent name lookup for foo in this case is no different from non-dependent lookup. It will not be able to see your foo, since it is declared below the template.

Note the difference with the following example

template <class T> void tfoo( T t )
{
    foo( t );
}

struct S {};

void foo(S s) {}

int main()
{
    S s;
    tfoo(s);
}

This code will compile since argument type S is a class type. It has an associated namespace - the global one - and it adds (nominates) that global namespace for dependent name lookup. Such ADL-nominated namespaces are seen by dependent lookup in their updated form (as seen from the point of the call). This is why the lookup can see foo and completes successfully.


It is a rather widespread misconception when people believe that the second phase of so called "two-phase lookup" should be able to see everything that was additionally declared below template definition all the way to the point of instantiation (point of the call in this case).

No, the second phase does not see everything. It can see the extra stuff only in namespaces associated with function arguments. All other namespaces do not get updated. They are seen as if observed from the point of template definition.

Clitoris answered 29/11, 2016 at 21:6 Comment(14)
If I change the type of T to be some class, then shouldn't that class' namespace also come in? it doesn't So I'm clearly not understanding something. Only if foo accepts the dependent type does it seem to also be brought in.Persis
@AndyG: It should. As i stated above, class types have associated namespaces. Fundamental types (like int) do not have associated namespaces. For this reason dependent lookup works differently for class types and fundamental types.Clitoris
@Persis You're calling foo with and int as an argument, there's no ADL going on.Shenika
If S is defined in a namespace does foo also will have to be defined in that namespace?Penelopepeneplain
Thanks. My misunderstanding is a little embarrassing actually, but I'll share in case anybody that comes across this was similarly confused. The function foo as an unqualified name will be looked up according to its own arguments, and since int is a fundamental type, the list of candidates for ADL is empty. I mistakenly believed that the arguments to tfoo would also be used for ADL to find foo, so if foo was in the same namespace as T, then it should be brought in.Persis
@AndyG: You are right. If foo was in the same namespace as actual T, then it should be brought in. This works for class types. This works for enum types. And so on (see 3.4.2). This does not work for fundamental types.Clitoris
@AnT, then can you explain why the code I linked in my first comment does not compile?Persis
@Persis well it somehow compiles when your struct has conversion operator to int... example Very nice explanation!Minni
@AndyG: Well, I'd assume that the pedantic explanation goes as follows: associated namespaces that are involved into the lookup for a call to foo are determined by the argument types used in that call to foo. Since in this example foo is called with an argument t.value of type int, there are no associated namespaces. The fact that that int came there through detail::adl_wrap does not make any difference. It is still just an int.Clitoris
Thanks again. So your previous comment was predicted on foo accepting T. I'm not totally crazy!Persis
@W.F.: It is a critical distinction: ADL is defined by the immediate argument type used in the call, before any conversions. Obviously, the compiler cannot know that the argument will be converted to int before it actually finds foo(int) and decides that foo(int) is the best (only) candidate.Clitoris
@W.F.: You previous comment (which you deleted for some reason) involved a function with a default argument. Default arguments (DA) cannot affect name lookup for the very same reason user-defined conversions (UDC) cannot affect name lookup. Both DA and UDC come into play during (or after) overload resolution. Overload resolution is only possible after name lookup is finished. For this reason DA and UDC cannot possibly affect name lookup. At name lookup stage, it is not yet known what DA and UDC are available.Clitoris
@AnT I thought that the case was already covered by the fact T is fundamental type which wouldn't involve foo namespace and thought it didn't bring anything fresh to the conversation :)Minni
The comment look more or less like: it still does not compile even after changing foo signature to void foo(int, S ={})Minni

© 2022 - 2024 — McMap. All rights reserved.