header 文件中是否允许函数模板的显式模板实例化定义

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

当我遇到以下内容时,我正在阅读有关显式模板实例化的内容 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.


我的问题是 上面关于不能将显式模板实例化定义放入header 文件(并且必须放入源文件)的说法在技术上是正确的。我正在寻找来自标准(或等效来源)的准确参考,其中指定这些 ETI 定义不能放入 header 文件。

我还在一个示例程序中尝试了这个,即使我已经将 ETI 放入 header,该程序也可以很好地编译和链接,而不会在 gcc 和 clang 中给出任何多重定义错误(演示)。 下面给出的程序well-formed是否符合标准?

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

来自 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.

(强调我的)

这意味着显示的有问题的程序违反了上面引用的声明,因此 ill-formed 不需要诊断


同样可以在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.

(强调我的)

这再次得出结论,即给定的示例程序是 ill-formed NDR。


因此只要ETI定义在整个程序中只出现一次,程序就是有效的。例如,如果您有一个具有 ETI 定义的 header,那么如果 header 包含在多个源文件中,则程序将是 ill-formed(注意一个可能的解决方法) here)。但是如果 header 恰好包含在一个源文件中,那么程序将是 well-formed。 重点是它们在程序中最多出现一次。它们来自哪里(如 header 或源文件)无关紧要。

技术上,可以在 header 中进行显式实例化 - 只要 header 仅包含一次即可。

更准确的说法是每个程序只能出现一次(这就是为什么标准确实如此)。

实际上这几乎是一个没有区别的区别,因为首先写这样的 header 没有多大意义。

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.

这种说法是不正确的,即使在 header 可能被允许包含在多个翻译单元中的一般假设下也是如此。

虽然[temp.spec.general]/5明确a显式实例化定义,意思是a特定的特化,只能出现一次一个程序

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.

这并不禁止将显式实例化放置在 header 中,即使在 header 可能包含在多个翻译单元中的一般假设下也是如此。不,我们只需要确保对于每个这样的翻译单元,显式实例化定义实例化 不同的 特化。如何?在 header 个文件中使用其他 anti-pattern 未命名的命名空间。

// 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>;

我们为什么要这样做?

这种特殊技术可用于利用 [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.

从 C++20 开始,我们将利用此 hack 的特化,但 pre-C++20 我们将使用显式实例化定义,因为它允许:

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.

因此,对于某些类型Foo定义如下:

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

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

    int x{42};
};

我们可以规避Foo的访问规则如下:

// 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>;

我们可以在特定的源文件中使用如下:

// 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
}

有关详细信息,请参见示例