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)?

Slava
  • 43,454
  • 1
  • 47
  • 90
  • isn't it because [the two phase lookup](http://stackoverflow.com/questions/7767626/two-phase-lookup-explanation-needed)? – W.F. Nov 29 '16 at 20:49
  • @W.F. actually I would expect this to compile because of two phase lookup. – Slava Nov 29 '16 at 20:51
  • 2
    Perhaps declaring the function you want to call inside the function template will solve the problem for you? – SirGuy Nov 29 '16 at 21:09
  • 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: http://coliru.stacked-crooked.com/a/dc92e572d02f601a – AndyG Nov 29 '16 at 23:00

1 Answers1

9

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • If I change the type of `T` to be some class, then shouldn't that class' namespace also come in? [it doesn't](http://coliru.stacked-crooked.com/a/19f9b4a7c8b9f0c0) So I'm clearly not understanding something. Only if `foo` accepts the dependent type does it seem to also be brought in. – AndyG Nov 29 '16 at 21:21
  • @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. – AnT stands with Russia Nov 29 '16 at 21:24
  • @AndyG You're calling `foo` with and `int` as an argument, there's no ADL going on. – vsoftco Nov 29 '16 at 21:24
  • If `S` is defined in a namespace does `foo` also will have to be defined in that namespace? – Slava Nov 29 '16 at 21:29
  • 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. – AndyG Nov 29 '16 at 21:30
  • 1
    @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. – AnT stands with Russia Nov 29 '16 at 21:37
  • 1
    @AnT, then can you explain why the code I linked in my first comment does not compile? – AndyG Nov 29 '16 at 21:40
  • @AndyG well it somehow compiles when your struct has conversion operator to int... [example](http://coliru.stacked-crooked.com/a/255b22bbe85e607a) Very nice explanation! – W.F. Nov 29 '16 at 21:50
  • @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`. – AnT stands with Russia Nov 29 '16 at 21:57
  • Thanks again. So your previous comment was predicted on foo accepting T. I'm not totally crazy! – AndyG Nov 29 '16 at 22:08
  • 1
    @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. – AnT stands with Russia Nov 29 '16 at 22:30
  • 1
    @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. – AnT stands with Russia Nov 30 '16 at 05:04
  • @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 :) – W.F. Nov 30 '16 at 05:46
  • The comment look more or less like: it still does not compile even after changing foo signature to `void foo(int, S ={})` – W.F. Nov 30 '16 at 06:00