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.