【问题标题】:Calling function templates specialized in another translation unit [duplicate]调用专门用于另一个翻译单元的功能模板[重复]
【发布时间】:2020-07-23 19:52:27
【问题描述】:

我正在开发一个使用以下结构的代码库:

啊哈:

template<int N> void f();
void b();

a.cpp:

#include "a.h"

template<> void f<1>() {}

int main()
{
    b();
}

b.cpp:

#include "a.h"
void b()
{
    f<1>();
}

代码似乎可以正确构建和运行。

我的问题是:这是格式正确的,还是某种格式错误的 NDR 碰巧有效?


如果使用clang -Wundefined-func-template 构建(在我的 IDE 的 clang-tidy 默认设置中启用),则会产生警告:

b.cpp:5:2: warning: instantiation of function 'f<1>' required here, but no definition is available [-Wundefined-func-template]
        f<1>();
        ^
./a.h:1:22: note: forward declaration of template entity is here
template<int N> void f();
                     ^
b.cpp:5:2: note: add an explicit instantiation declaration to suppress this warning if 'f<1>' is explicitly instantiated in another translation unit
        f<1>();
        ^

但我不确定是否只是禁用警告,或进行一些代码更改(除了将显式专业化定义移动到头文件,这对于这个项目来说不是更可取的)。

遵循警告消息中的建议并将显式实例化声明添加到头文件(即extern template void f&lt;1&gt;();)会导致错误消息(在显式实例化之前隐式实例化特化)。

但是,向头文件添加显式特化声明 template&lt;&gt; void f&lt;1&gt;(); 会抑制警告。但我不确定这是否是 (a) 必要的,和/或 (b) 推荐的样式。

【问题讨论】:

标签: c++ templates language-lawyer one-definition-rule explicit-specialization


【解决方案1】:

程序违反[temp.expl.spec]/6

如果模板、成员模板或类模板的成员被显式特化,则该特化应在第一次使用该特化之前声明,这将导致隐式实例化发生,在每个翻译单元中发生使用;不需要诊断。

函数模板f 被b.cpp 中的template&lt;&gt; void f&lt;1&gt;() {} 明确特化。但是在由 b.cpp 和包括 ah 组成的翻译单元中,语句 f&lt;1&gt;(); 将导致同一特化 f&lt;1&gt; 的隐式实例化,并且在翻译单元中更早(或任何地方)没有显式特化的声明.

根据标准,显式特化始终与实例化特化不同,因为对于相同的主模板和相同的模板参数,两者永远不会存在。但该程序可能仍然可以工作,因为许多编译器对模板显式特化和实例化特化使用相同的错位链接器名称。

clang 警告可能是因为它是合法的,虽然不寻常,如果相同的特化在其他地方显式实例化,而不是显式特化,则隐式实例化没有可见定义的函数模板。因此,它建议进行改进以使法律程序更加清晰。不过,我不确定它是否真的合法。但它建议的显式实例化声明是一个谎言,因为特化是显式特化的,而不是显式实例化的。

如果您将显式的特化声明添加到每个将要使用的特化的头文件中,程序就会生效。

template<int N> void f();
template<> void f<1>();

【讨论】:

  • f&lt;1&gt;() 不会导致发生隐式实例化。它不能,因为没有提供主模板的定义。
  • @IgorTandetnik 引用说“会导致”。它没有准确地命名“将”的假设条件,但这可能是措辞的原因之一。并且 [temp.inst]/4 “当在需要定义存在的上下文中引用特化时,函数模板特化被隐式实例化”没有被声明为以模板定义为条件,因此无论是否存在隐式实例化格式不正确,我会说这至少会导致“会导致隐式实例化”条件。
  • 我不明白这种解释如何与 [temp.over]/5 相协调。似乎对非成员函数的调用本身并不需要定义存在。还有[temp.inst]/8:“如果函数模板或成员函数模板特化以涉及重载决议的方式使用,则特化的声明是隐式实例化。” (强调我的)
  • 我想我现在可以解释警告消息:无论f&lt;1&gt; 恰好像我的问题中那样明确专门化,或者a.cpp 包含f&lt;int&gt; 的定义,都使用相同的消息以及 f&lt;1&gt; 的显式实例化。我猜他们不想为这两种类型的问题提供两种不同的警告条件
  • @IgorTandetnik [temp.over]/5 和 [temp.inst]/8 都在谈论重载解决方案。实例化声明以进行重载解析,但如果模板特化是最佳功能并且上下文是 odr-use,则 ODR 需要存在定义。另一个转折点:[temp.inst]/1 暗示有时隐式实例化以前未定义的类模板可能是有效的。
【解决方案2】:

[temp.over]/5 中有一个例子几乎完全匹配你的,并且发音良好:

[temp.over]/5 ... [ 示例:

template<class T> void f(T); // declaration
void g() {
  f("Annemarie"); // call of f<const char*>
}

f 的调用是格式良好的,即使模板 f 仅在调用时声明且未定义。除非在某些翻译单元中存在隐式或显式生成的 f&lt;const char*&gt; 的特化,否则程序将是格式错误的。 ——结束示例 ]

【讨论】:

    【解决方案3】:

    [temp]/7 说:

    函数模板、类模板的成员函数、变量模板或类模板的静态数据成员应在隐式实例化的每个翻译单元中定义,除非相应的特化显式实例化 在某些翻译单元中;不需要诊断。

    标准要求显式实例化,因此a.cpp 中的显式特化不会使程序结构良好。

    CWG2138 中提出了类似的问题([temp]/7 平等对待函数模板和类模板的成员函数):

    目前的规则尚不清楚以下常见做法是否有效:

       // foo.h
       template<typename T> struct X {
        int f(); // never defined
       };
    
       // foo.cc
       #include "foo.h"
       template<> int X<int>::f() { return 123; }
    
       // main.cc
       #include "foo.h"
       int main() { return X<int>().f(); }
    

    作为 NAD 关闭的原因如下:

    正如分析中所述 [其中提到了 [temp]/7 等],其目的是让示例格式错误,无需诊断。

    所以,答案是:该程序是格式错误的 NDR,这是有意为之的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-13
      • 1970-01-01
      • 2015-06-04
      • 2019-06-21
      相关资源
      最近更新 更多