Search code examples
c++c++20c++-conceptstemplate-instantiation

Explicit template instantiation vs concept constraints


Let's suppose that I've either a function or a class template that should work only for certains types, e.g. std::wstring and std::string.

I know that concepts can be used to put a constraint on a template so I would use something like this:

template <typename T>
concept StringLike = std::convertible_to<T, std::wstring> || std::convertible_to<T, std::string>;

template<StringLike S>
class A
{
public:
 A(const S& data)
 : data_{data}
{};

private:
 S data_;
}

However, I was also thinking about template (class, in this case) instantiation which should achieve the same goal.

template<typename S>
class A
{
public:
 A(const S& data)
 : data_{data}
{};

private:
 S data_;
}

template class A<std::string>;
template class A<std::wstring>;

If I understood their exchangeability correctly, when should I use one instead of the other?

PS. I know that std::convertible_to<T, std::string> is not exactly the same as template class A<std::string> since the first is less constrained than the latter but it's just for the sake of the example.


Solution

  • You have two things here and they are not interchangeable.

    Concepts are used to constrain template arguments. This is what you should use if you want to restrict the type arguments your A class should be instantiated with.

    Explicit template instantiation, that you show in the second example, is something different. Remember that templates are not classes or functions but blueprints from which the compiler generates code (with a particular set of template arguments). Typically, this would happen when the compiler encounters the use of a template in your code (in each translation unit). This is called implicit template instantiation. However, you can also tell the compiler to instantiate a template from a set of arguments. This is called explicit template instantiation. This is what you did here:

    template class A<std::string>;
    template class A<std::wstring>;
    

    With your example, this would make no difference, you could still instantiate A<int>. Example:

    #include <string>
    
    template<typename S>
    class A
    {
    public:
     A(const S& data)
     : data_{data}
    {};
    
    private:
     S data_;
    };
    
    template class A<std::string>;
    template class A<std::wstring>;
    
    int main()
    {
        A<std::string> a1("42");
        A<std::wstring> a2(L"42");
        A<int> a(42);
    }
    

    However, let's say you have this:

    A.h

    #pragma once
    
    template<typename S>
    class A
    {
    public:
       A(const S& data);
    
    private:
       S data_;
    };
    

    A.cpp

    #include "A.h"
    #include <string>
    
    template <typename S>
    A<S>::A(const S& data) : data_{data}
    {}
    
    template class A<std::string>;
    template class A<std::wstring>;
    

    and finally: main.cpp

    #include "A.h"
    #include <string>
    
    int main()
    {
        A<std::string> a1("42");
        A<std::wstring> a2(L"42");
        A<int> a(42); // error
    }
    

    Since the definition of A's constructor is in another translation unit (A.cpp), you cannot use it in main.cpp. However, you explicitly instantiated A for std::string and std::wstring and that makes them available outside that translation unit.

    Explicit template instantiation has two forms:

    • explicit instantiation declaration: this is used to suppress an implicit instantiation. Such a declaration is introduced with the extern keyword. This tells the compiler that the definition is found in another translation unit (or later in the same translation unit). The purpose is to reduce compilation time by generating a single instantiation in a single translation unit. See https://isocpp.org/wiki/faq/cpp11-language-templates#extern-templates.
    • explicit instantiation definition: forces the instantiation of the type it refers to. This must occur is the namespace of the template.