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

bogdan
  • 9,229
  • 2
  • 33
  • 48
user1289
  • 1,251
  • 2
  • 13
  • 25
  • 2
    "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](https://en.wikipedia.org/wiki/C%2B%2B#History) So it didn't change between `C++11` and `C++14`. It changed between "pre-standardization" and standardization. – bolov Oct 10 '16 at 13:28
  • Check 14.6.4.1 [temp.point] for the rules – AndyG Oct 10 '16 at 13:30
  • also search for two phase name lookup – bolov Oct 10 '16 at 13:31
  • @bolov I'm reading third edition (1997). – user1289 Oct 10 '16 at 13:42

1 Answers1

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

bolov
  • 72,283
  • 15
  • 145
  • 224
  • 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? – user1289 Oct 10 '16 at 14:01
  • 1
    @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]) – AndyG Oct 10 '16 at 14:03
  • @AndyG "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. – user1289 Oct 10 '16 at 14:06
  • @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. – AndyG Oct 10 '16 at 14:08
  • 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. – user1289 Oct 10 '16 at 14:30
  • @user1289 you were right. `g` is dependent. But as far as I can tell, ADL works for user defined types only. – bolov Oct 10 '16 at 14:40
  • @user1289 found and updated about ADL, which is basically what AngyG said. – bolov Oct 10 '16 at 14:49