Search code examples
c++templatesc++14name-bindingtemplate-instantiation

At which point occurs template Instantiation binding?


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


Solution

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