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

发布于 2025-01-31 11:57:12 字数 1554 浏览 2 评论 0 原文

当我遇到以下答案

通过“显式模板实例化”假设您的意思是

 模板类foo< int>; //显式类型的实例化
   // 或者
   模板void foo< int>(); //显式功能实例化
 

然后这些必须在源文件中,因为它们考虑了定义,因此受到 odr


我的问题是,那就是上述说明不能将明确的模板实例化定义定义在技术上是正确的(必须放入源文件中)。我正在寻找来自标准(或同等源)的确切参考,其中可以指定这些ETI定义不能放入标头文件中。

我还在示例程序中尝试了此程序,该程序可以很好地编译和链接,而无需在GCC和Clang中给出任何多重定义错误(DEMO),即使我将ETIS放入标题中。 程序是否根据标准

以下给定的

#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

#include "Header.h"

#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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

软甜啾 2025-02-07 11:57:12

我的问题是上面的说法,即显式模板实例化定义不能将其放入标题文件(必须放入源文件中)在技术上正确。

该陈述是不正确的,即使在一般假设下,可以允许将标头包含在几个翻译单元中。

[temp.spec.general]/5 很明显, a 显式实例化定义,含义 a 特定专业化,只能出现一次

对于给定的模板和一组给定的模板arguments,

  • (5.1)明确的实例定义在程序中最多出现一次
  • (5.2)按照[Basic.def.odr]中指定的程序中最多定义明确的专业化,
  • (5.3)除非显式
    实例化遵循明确专业的声明。

不需要实施来诊断违反此事
规则。

这并不能阻止明确的实例化放置在标头中,即使在总体假设下,标头可能包含在几个翻译单元中。不,我们只需要确保,对于每个这样的翻译单元,显式实例定义实例化不同的专门。如何?使用标题文件中未命名的名称空间的反图案。

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

我们为什么要这样做?

该特定技术可用于通过利用

通常的访问检查规则不适用于声明明确的实例化或显式专业化的名称,除了函数主体中出现的名称,默认参数,基本 - 子句,成员规格,枚举列表,枚举 - 列表,枚举列表,或静态数据成员或可变模板初始化器。

从C ++ 20开始,我们将利用该黑客的专业知识,但是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
}

有关详细信息,请参见eg

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.

审判长 2025-02-07 11:57:12

在技术上,可以在标题中具有明确的实例化是可以的 - 只要该标头仅包含一次即可。

更准确地说它只能出现每个程序一次(这就是为什么标准确实可以做到这一点)。

实际上这几乎是一个区别,没有差异,因为首先写下这样的标题并不多。

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.

花开半夏魅人心 2025-02-07 11:57:12

来自显式实例化文档

明确的实例化定义迫使他们所指的类,结构或工会的实例化。它可能出现在模板定义之后的任何位置中的程序中,对于给定参数列表,只能出现在整个程序中一次,而无需诊断。

(强调我)

这意味着这意味着所示的程序违反了上述引用的陈述,因此不正确的诊断


也可以在 temp.spec.spec

对于给定的模板和一组给定的模板arguments,

  • 明确的实例定义应在程序中最多出现一次

不需要实施以诊断违反此规则

(强调矿山)

这再次得出结论,即给定的示例程序是 ndr。


因此,只要ETI定义在整个程序中仅出现一次,该程序是有效的。例如,如果您的标头具有具有ETI定义的标题,则如果将标头包含在一个以上的源文件中,则该程序将不正确(请注意此在这里)。但是,如果将标题包含在一个源文件中,则该程序将被构成良好。 重点是他们最多应该在程序中出现。它们来自(例如标题或源文件)是无关紧要的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文