Are explicit template instantiation definition for a function template allowed in header files

Viewed 134

I was reading about explicit template instantiation when i came across the following answer:

Assuming by "explicit template instantiation" you mean something like

   template class Foo<int>; // explicit type instantiation
   // or
   template void Foo<int>(); // explicit function instantiation

then these must go in source files as they considered definitions and are consequently subject to the ODR.


My question is that is the above claim that explicit template instantiation definition cannot be put into header files(and must be put into source files) technically correct. I am looking for an exact reference from the standard(or equivalent source) where it is specified that these ETI definitions cannot be put into header files.

I also tried this in a sample program which compiles and links fine without giving any multiple definition error(demo) in both gcc and clang even though i have put the ETIs into the header. Is the below given program well-formed according to the standard?

Header.h

#ifndef MYHEADER_H
#define MYHEADER_H
#include <string>

template<class T>
int func( const T& str)
{
  return 4;
}
template int func<std::string>( const std::string& str); //first ETI in header. Will the program be well formed if this header is included in multiple source files?
template int func<double>(const double& d);              //second ETI in header


#endif

source2.cpp

#include "Header.h"

source3.cpp

#include "Header.h"

main.cpp


#include <iostream>
#include "Header.h"
int main(){
  std::string input = "123";

  auto result = func(input);
    std::cout<<result<<std::endl;

}

Demo

3 Answers

My question is that is the above claim that explicit template instantiation definition cannot be put into header files(and must be put into source files) technically correct.

This statement is not correct, even under the general assumption that the header may be allowed to be included in several translation units.

Whilst [temp.spec.general]/5 is clear that a explicit instantiation definition, meaning for a specific specialization, must only appear once in a program

For a given template and a given set of template-arguments,

  • (5.1) an explicit instantiation definition shall appear at most once in a program,
  • (5.2) an explicit specialization shall be defined at most once in a program, as specified in [basic.def.odr], and
  • (5.3) both an explicit instantiation and a declaration of an explicit specialization shall not appear in a program unless the explicit instantiation follows a declaration of the explicit specialization.

An implementation is not required to diagnose a violation of this rule.

This does not bar explicit instantiation from being placed in header, even under the general assumption that the header may be included in several translation units. No, we simply need to assure that for each such translation unit, the explicit instantiation definition instantiates different specializations. How? Using the otherwise anti-pattern of unnamed namespaces in header files.

// some_header.h
#pragma once

// Unique namespace in each TU.
namespace {
// Unique type in each TU (internal linkage).
struct TranslationUnitTag {};
}  // namespace

template<typename Tag>
struct Dummy{};

// This is fine, as it is a different specialization
// in every separate translation unit.
template struct Dummy<TranslationUnitTag>;

Why would we ever want to do this?

This particular technique can be used to circumvent private access rules by leveraging [temp.spec.general]/6

The usual access checking rules do not apply to names in a declaration of an explicit instantiation or explicit specialization, with the exception of names appearing in a function body, default argument, base-clause, member-specification, enumerator-list, or static data member or variable template initializer.

As of C++20 we would leverage specializations for this hack, but pre-C++20 we'd use explicit instantiation definitions, as it allows:

class A { int x; };

template<auto>
class B {};

// Explicit instantiation definition.
template class B<&A::x>;
//               ^^^^^ access rules for private data
//                     member 'x' is waived in an
//                     explicit instantiation definition.

Thus, for some type Foo defined as follows:

// foo.h
#pragma once
#include <iostream>

class Foo {
    int bar() const {
        std::cout << __PRETTY_FUNCTION__;
        return x;
    }

    int x{42};
};

we may circumvent the access rules of Foo as follows:

// access_private_of_foo.h
#pragma once
#include "foo.h"

// Unique namespace in each TU.
namespace {
// Unique type in each TU (internal linkage).
struct TranslationUnitTag {};
}  // namespace

// 'Foo::bar()' invoker.
template <typename UniqueTag,
          auto mem_fn_ptr>
struct InvokePrivateFooBar {
    // (Injected) friend definition.
    friend int invoke_private_Foo_bar(Foo const& foo) {
        return (foo.*mem_fn_ptr)();
    }
};
// Friend (re-)declaration.
int invoke_private_Foo_bar(Foo const& foo);

// Single explicit instantiation definition.
template struct InvokePrivateFooBar<TranslationUnitTag, &Foo::bar>;

// 'Foo::x' accessor.
template <typename UniqueTag,
          auto mem_ptr>
struct AccessPrivateMemFooX {
    // (Injected) friend definition.
    friend int& access_private_Foo_x(Foo& foo) {
        return foo.*mem_ptr;
    }
};
// Friend (re-)declaration.
int& access_private_Foo_x(Foo& foo);

// Single explicit instantiation definition.
template struct AccessPrivateMemFooX<TranslationUnitTag, &Foo::x>;

which we may use in a particular source file as follows:

// demo.cpp
#include <iostream>
#include "access_private_of_foo.h"
#include "foo.h"

void demo() {
    Foo f{};

    int hidden = invoke_private_Foo_bar(f);  // int Foo::bar() const
    std::cout << hidden << "\n";  // 42

    access_private_Foo_x(f) = 13;
    hidden = invoke_private_Foo_bar(f);  // int Foo::bar() const
    std::cout << hidden << "\n";  // 13
}

For details, see e.g.

From explicit instantiation's documentation:

An explicit instantiation definition forces instantiation of the class, struct, or union they refer to. It may appear in the program anywhere after the template definition, and for a given argument-list, is only allowed to appear once in the entire program, no diagnostic required.

(emphasis mine)

This means that the shown program in question is in violation of the above quoted statement and thus ill-formed no diagnostic required.


The same can be found in temp.spec:

For a given template and a given set of template-arguments,

  • an explicit instantiation definition shall appear at most once in a program

An implementation is not required to diagnose a violation of this rule.

(emphasis mine)

This again leads to the conclusion that the given example program is ill-formed NDR.


Thus as long as the ETI definition occurs only once in the entire program the program is valid. For example, if you have a header that has the ETI definitions, then the program will be ill-formed if that header is included in more than one source file(note a possible workaround for this here). But if the header is included in exactly one source file then the program will be well-formed. The point is that they should appear at most once in the program. From where they come from(like header or a source file) is irrelevant.

Technically, it's fine to have an explicit instantiation in a header - so long as that header is only included once.

It's more accurate to say it can only occur once per program (which is why the standard does exactly that).

Practically this is almost a distinction without a difference, as there's not much point writing such a header in the first place.

Related