At which point occurs template Instantiation binding?
Asked Answered
C

1

21

This code is from "C++ programming language" by Bjarne Stroustrup (C.13.8.3 Point of Instantiation Binding)

template <class T>
void f(T value)
{
    g(value);
}

void g(int v);

void h()
{
    extern g(double);
    f(2);
}

And he mentions:

Here, the point of instantiation for f() is just before h(), so the g() called in f() is the global g(int) rather than the local g(double). The definition of ‘‘instantiation point’’ implies that a template parameter can never be bound to a local name or a class member.

void h()
{
    struct X {}; // local structure
    std::vector<X> v; // error: can't use local structure as template parameter
}

My questions are:

  1. Why should the first code work? g() is declared later, and I really get an error with G++ 4.9.2 that g isn't declared at that point.

  2. extern g(double) - how this works? since return value doesn't matter in case of function overloading, then we can miss it in forward declarations?

  3. the point of instantiation for f() is just before h() - why? isn't it logical that it'll get instantiated when f(2) is being called? Right where we call it, whence g(double) will be in scope already.

  4. The definition of ‘‘instantiation point’’ implies that a template parameter can never be bound to a local name or a class member - Has this changed in C++14? I'm getting error with C++(G++ 4.9.2), but don't get error with C++14(G++ 4.9.2).

Coltish answered 10/10, 2016 at 13:9 Comment(4)
"In 1985, the first edition of The C++ Programming Language was released, which became the definitive reference for the language, as there was not yet an official standard." wiki So it didn't change between C++11 and C++14. It changed between "pre-standardization" and standardization.Langston
Check 14.6.4.1 [temp.point] for the rulesCrisscross
also search for two phase name lookupLangston
@Langston I'm reading third edition (1997).Coltish
L
15

"In 1985, the first edition of The C++ Programming Language was released, which became the definitive reference for the language, as there was not yet an official standard." wiki C++ History So it didn't change between C++11 and C++14. I can assume (and please take this with a grain of salt) it changed between "pre-standardization" and standardization. Maybe someone who knows better the history of C++ can shed more light here.

As for what actually happens:


First let's get out of the way the simple one:

extern g(double);

This is invalid C++. Historically, unfortunately C allowed omission of type. In C++ you have to write extern void g(double).


Next, let's ignore the g(double) overload to answer your first question:

template <class T>
void f(T value)
{
    g(value);
}

void g(int v);

int main()
{
    f(2);
}

In C++ there is the infamous two phase name lookup:

  • In the first phase, at the template definition, all non-dependent names are resolved. Failure to do so is a hard error;
  • Dependent names are resolved in phase two, at the template instantiation.

The rules are a bit more complicated, but that is the gist of it.

g is dependent on template parameter T so it passes the first phase. That means that if you never instantiate f, the code compiles just fine. At the second phase f is instantiated with T = int. g(int) is now searched, but not found:

17 : error: call to function 'g' that is neither visible in the template definition nor found by argument-dependent lookup
g(value);
^
24 : note: in instantiation of function template specialization 'f<int>' requested here
f(2);
^
20 : note: 'g' should be declared prior to the call site
void g(int v);

In order for an arbitrary name g to pass with flying colors we have a few options:

  1. Declare g previously:
void g(int);

template <class T>
void f(T value)
{
    g(value);
}
  1. bring g in with T:
template <class T>
void f(T)
{
    T::g();
}

struct X {
   static void g();
};

int main()
{
    X x;
    f(x);
}
  1. Bring g in with T via ADL:
template <class T>
void f(T value)
{
    g(value);
}

struct X {};

void g(X);

int main()
{
    X x;
    f(x);
}

These of course change the semantics of the program. They are meant to illustrate what you can and cannot have in a template.


As for why doesn't ADL find g(int), but finds g(X):

§ 3.4.2 Argument-dependent name lookup [basic.lookup.argdep]

  1. For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered [...]:

    • If T is a fundamental type, its associated sets of namespaces and classes are both empty.

    • If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces of which its associated classes are members. [...]


And finally we get to why extern void g(double); inside main is not found: first of all we showed that g(fundamental_type) is found iff it is declared prior to the f definition. So let's make it void g(X) inside main. Does ADL find it?

template <class T>
void f(T value)
{
    g(value);
}

struct X{};


int main()
{
  X x;
  void g(X);

  f(x);
}

No. Because it does not reside in the same namespace as X (i.e. global namespace) ADL can't find it.

Proof that g is not in global

int main()
{
  void g(X);

  X x;
  g(x); // OK
  ::g(x); // ERROR
}

34 : error: no member named 'g' in the global namespace; did you mean simply 'g'?

Langston answered 10/10, 2016 at 13:45 Comment(7)
Thank you for the answer. I have two question. 1. Why in first example g is a non-dependent? It's dependent from T (in this case int), with ADL it may be found in the namespace T is defined (for built in types namespace is supposed to be the global namespace, as far as I know). 2. With your logic, why f(x) works, but f(2) doesn't work with ADL?Coltish
@user1289: f(2) doesn't work with ADL because 2 is int, which is a fundamental type, and "1) For arguments of fundamental type, the associated set of namespaces and classes is empty" (See 3.9.1 Fundamental types [basic.fundamental])Crisscross
@Crisscross "The binding of dependent names is done by looking at the names in the namespace of an argument of a dependent call (global functions are considered in the namespace of built-in types" - this is from the same book.Coltish
@user1289: But int doesn't even bring in global functions. See bolov's part about declaring g before the template. Also, I think you should read a more recent book. You're asking C++14 questions using a book that was published before the C++03 standard.Crisscross
I didn't found "For arguments of fundamental type, the associated set of namespaces and classes is empty" in documentation. Although, as you mentioned, f(2) doesn't compile for me, and f(x) compiles.Coltish
@Coltish you were right. g is dependent. But as far as I can tell, ADL works for user defined types only.Langston
@Coltish found and updated about ADL, which is basically what AngyG said.Langston

© 2022 - 2024 — McMap. All rights reserved.