c++visual-studiotemplatesgcc

Template instantiation details of GCC and MS compilers


Could anyone provide a comparison or specific details of how is template instantiation handled at compile and/or link time in GCC and MS compilers? Is this process different in the context of static libraries, shared libraries and executables? I found this doc about how GCC handles it but I'm not sure if the information is still referring to the current state of things. Should I use the flags they suggest there when compiling my libraries e.g. -fno-implicit-templates?

What I know (might not necessarily be correct) is that:


Solution


  • Point of instantiation

    templates will be instantiated when actually used

    Not exactly, but roughly. The precise point of instantiation is a bit subtle, and I delegate you over to the section named Point of instantiation in Vandevoorde's/Josuttis' fine book.

    However, compilers do not necessarily implement the POIs correctly: Bug c++/41995: Incorrect point of instantiation for function template


    Partial instantiation

    templates will be instantiated when actually used

    That is partially correct. It is true for function templates, but for class templates, only the member functions that are used are instantiated. The following is well-formed code:

    #include <iostream>
    
    template <typename> struct Foo {
        void let_me_stay() {
            this->is->valid->code. get->off->my->lawn;
        }
    
        void fun() { std::cout << "fun()" << std::endl; } 
    };
    
    
    int main () {
        Foo<void> foo;
        foo.fun();
    }
    

    let_me_stay() is checked syntactically (and the syntax there is correct), but not semantically (i.e. it is not interpreted).


    Two phase lookup

    However, only dependent code is interpreted later; clearly, within Foo<>, this is dependent upon the exact template-id with which Foo<> is instantiated, so we postponed error-checking of Foo<>::let_me_alone() until instantiation time.

    But if we do not use something that depends on the specific instantiation, the code must be good. Therefore, the following is not well-formed:

    $ cat non-dependent.cc
    template <typename> struct Foo {
        void I_wont_compile() { Mine->is->valid->code. get->off->my->lawn; }
    };
    int main () {} // note: no single instantiation
    

    Mine is a completely unknown symbol to the compiler, unlike this, for which the compiler could determine it's instance dependency.

    The key-point here is that C++ uses a model of two-phase-lookup, where it does checking for non-dependent code in the first phase, and semantic checking for dependent code is done in phase two (and instantiation time) (this is also an often misunderstood or unknown concept, many C++ programmers assume that templates are not parsed at all until instantiation, but that's only myth coming from, ..., Microsoft C++).


    Full instantiation of class templates

    The definition of Foo<>::let_me_stay() worked because error checking was postponed to later, as for the this pointer, which is dependent. Except when you would have made use of

    explicit instantiations

    cat > foo.cc
    #include <iostream>
    
    template <typename> struct Foo {
        void let_me_stay() { this->is->valid->code. get->off->my->lawn; }
        void fun() { std::cout << "fun()" << std::endl; } 
    };
    
    template struct Foo<void>;
    int main () {
        Foo<void> foo;
        foo.fun();
    }
    
    g++ foo.cc
    error: error: ‘struct Foo<void>’ has no member named ‘is’
    


    Template definitions in different units of translation

    When you explicitly instantiate, you instantiate explicitly. And make all symbols visible to the linker, which also means that the template definition may reside in different units of translation:

    $ cat A.cc
    template <typename> struct Foo {
        void fun();  // Note: no definition
    };
    int main () {
        Foo<void>().fun();
    }
    
    $ cat B.cc
    #include <iostream>
    template <typename> struct Foo {
        void fun();
    
    };
    template <typename T>
    void Foo<T>::fun() { 
        std::cout << "fun!" << std::endl;
    }  // Note: definition with extern linkage
    
    template struct Foo<void>; // explicit instantiation upon void
    
    $ g++ A.cc B.cc
    $ ./a.out
    fun!
    

    However, you must explicitly instantiate for all template arguments to be used, otherwise

    $ cat A.cc
    template <typename> struct Foo {
        void fun();  // Note: no definition
    };
    int main () {
        Foo<float>().fun();
    }
    $ g++ A.cc B.cc
    undefined reference to `Foo<float>::fun()'
    

    Small note about two-phase lookup: Whether a compiler actually implements two-phase lookup is not dictated by the standard. To be conformant, however, it should work as if it did (just like addition or multiplication do not necessarily have to be performed using addition or multiplication CPU instructions.