Search code examples
c++template-instantiation

If a function definition has a parameter of class template type and didn't use it (its members) then is it instantiated?


From the previous example I've posted here about when the template is instantiated?, I got the answer that only when a template is used the compiler instantiates it. But look at this example:

template <typename T>
struct Pow{
    T operator()(T const& x){ return x * x; }
};

extern template struct Pow<int>; // explicit instantiation declaration

struct Foo{
    Pow<int> pi{};
    void fn(Pow<int>);
};
void Foo::fn(Pow<int> pw){
   // std::cout << pw(7) << '\n';
}

void bar(Pow<int> pwi){
    // std::cout << pwi(10) << '\n';
}

int main(){
    Foo f;
}
  • As you can see I've declared an explicit template instantiation Pow<int> but haven't defined it. The program works just fine and doesn't complain about the missing definition of Pow<int>!

  • In the previous topic I've been answered if I use a template type as a type of parameter for a function definition (not declaration) then the template is instantiated but here as you can see: The member function Foo::fn(Pow<int>) and the ordinary function function bar(Pow<int>) are defined but the compiler doesn't complain about the definition of Pow<int>?!!!

  • The program fails to compile if I un-comment the lines in the aforementioned functions. so does it mean that Pow<int> is not instantiated when used as function parameter in the function definition and as member data like in Foo::Pow<int> pi{};?

  • I find it confusing:

    void f(Pow<int>); // function declaration: Pow<int> is not instantiated yet.
    void f2(Pow<int>){} // function definition: Pow<int> instantiated? 
    void f3(pow<int> Pwi){ std::cout << Pwi(10);} // function definition and usage of `Pow<int>`: Pow<int> instantiated?
    
  • In main:

    Foo f; // f has pi of type Pow<int>. so Pow<int> is instantiated? 
    

Solution

  • The program works just fine and doesn't complain about the missing definition of Pow<int>!

    Because it isn't missing. Both forms of explicit instantiation (declaration and definition) cause the instantiation of class templates. An explicit instantiation definition causes the instantiation of member functions (which are normally instantiated lazily only when needed). On the other hand, an explicit instantiation declaration will suppress the implicit instantiation of a member function. Even if that member function body is visible!

    It's a tool to write templates for a constrained set of types, while hiding their implementation. It allows one to do this:

    //pow.h
    
    template <typename T>
    struct Pow{
        T operator()(T const& x); // Just a declaration 
    };
    
    extern template struct Pow<int>; // The class is instantiated, the member is 
                                     // assumed to be explicitly instantiated elsewhere
    
    //pow.cpp
    #include "pow.h"
    
    template <typename T>
    T Pow<T>::operator()(T const& x) { return x * x; }
    
    template struct Pow<int>; // Explicit instantiation. The member function is defined
                              // in **this** translation unit.
    

    And that is exactly why your program fails to link. The member operator() is never instantiated anywhere in your program. You can fix it by providing an explicit instantiation definition somewhere. For instance

    template <typename T>
    struct Pow{
        T operator()(T const& x){ return x * x; }
    };
    
    // ...
    
    int main() {
    // ...
    }
    
    template struct Pow<int>; // Now the member function is emitted
    

    Another use of this can be to potentially improve compile times. If you have a class template that you know is often instantiated for a specific set of types, you can help the linker along.

    // my_string.h
    
    template<typename charT>
    class my_string {
      // Everything, members and all
    };
    
    extern template class my_string<char>;
    extern template class my_string<wchar_t>;
    
    // my_string.cpp 
    
    #include <my_string.h>
    
    // This file can be part of a shared object that ships with your library
    // The common declarations are here
    
    template class my_string<char>;
    template class my_string<wchar_t>;
    

    Now the linker doesn't have to sort the many duplicate symbols that would have been produced by implicit instantiation of the commonly used my_string<char> and my_string<wchar_t>.