【问题标题】:C++ template specialization priority. Templated specializationC++ 模板专业化优先级。模板化专业化
【发布时间】:2020-07-18 00:25:08
【问题描述】:

主题看起来有点混乱,但是我不知道如何更恰当地表述它,对不起=)

我们看下面的代码

#include <iostream>

template<typename T>
void f(T value) {
  std::cout << "f<T>" << std::endl;
}

template<>
void f(int value) {
  std::cout << "f<int>" << std::endl;
}

template<typename T>
struct S {
  using type = T;
};

template<typename T>
void f(typename S<T>::type value) {
  std::cout << "f<S<T>>" << std::endl;
};

int main() {
  f(123);
  f<int>(123);
}

输出是

$ ./testgcc 
f<int>
f<S<T>>

所以问题是为什么第一次调用会导致f&lt;int&gt; 特化,而第二次使用显式int 模板参数会导致调用“模板化”f&lt;S&lt;int&gt;&gt;()?标准中是否有规定如何在这种情况下实例化模板?

提前致谢!

PS 使用不同版本的 gcc 和 clang 进行测试 - 行为是相同的。我没有 Windows 系统可以用 MSVC 进行测试,但是我在 Godbolt 上进行了测试,MSVC 结果如下:

_main   PROC
        ; ....
        push    123                           ; 0000007bH
        call    void f<int>(int)                      ; f<int>
        add     esp, 4
        push    123                           ; 0000007bH
        call    void f<int>(int)                      ; f<int>
        ; ...

所以 MSVC 在这两种情况下都会调用 f&lt;int&gt;。此行为是否记录为实现定义

【问题讨论】:

  • 这已涵盖here。两者在功能上等价,因为“对于任何给定的模板参数集,两个表达式的求值结果相同”。然后稍后:“如果程序包含功能等价但不等价的函数模板声明,则该程序格式错误;不需要诊断。”
  • 如果int 不应该,那么哪些类型应该使用 S 版本?你如何决定?
  • “此行为是否记录为实现定义?” 不,它要么是编译器中的错误,要么是格式错误的程序 NDR 或 UB。

标签: c++ templates


【解决方案1】:

你这里有 UB。

与类不同,模板函数不能部分特化。 模板函数可以像你写的那样完全特化

template<>
void f(int value) {
  std::cout << "f<int>" << std::endl;
}

但“部分专业化”往往会导致 UB,因为它被视为不相关的声明

template<typename T>
void f(T value) {
  std::cout << "f<T>" << std::endl;
}

template<typename T>
void f(typename S<T>::type value) {
  std::cout << "f<S<T>>" << std::endl;
};
// These two are conflicting declarations for "f" and compiler has no way to disambiguate.
// Worse due to nature of templates it tends to fail to figure out that there is ambiguity. 
// How to differentiate the two anyways?

使用 SFINAE 显式声明 f 的每个声明与哪些类型名相关,因此不存在冲突。

【讨论】:

  • 我对您将如何使用 SFINAE 感兴趣?你有时间解释吗?
  • @JerryJeremiah SFINAE 很简单,但语法非常不友好。这个想法是你添加额外的类型名作为由其他人确定的条件 - 如果类型名被证明是不可编译的废话,那么声明格式错误并被忽略。您最好寻找有关编写 SFINEA 代码的在线指南 - 有关于该主题的指南和视频。
  • @JerryJeremiah 如果您指的是帖子中的完全相同的情况-那么它本质上是毫无意义的。但是,如果您只是想要一些没有格式错误的东西,那么只需在类型名称 T 上召唤一些任意条件 A 并在 A 为真时启用第一个声明,并在 A 为假时启用第二个声明。
  • ""部分专业化"往往会导致 UB"。模板重载通常很好,aliasingint 导致 2 f&lt;int&gt;(int) 有问题。
  • @Jarod42 重载意味着您​​拥有同名但输入参数不同的函数。 “部分专业化”意味着您对同一个模板对象有两种不同的定义,只是其中一个比另一个更专业 - 因此是首选。模板类支持它,但函数不支持。
【解决方案2】:

让我们从简单的案例开始:f(123);

template&lt;typename T&gt; void f(typename S&lt;T&gt;::type)中,不能推导出T

所以只有一个可行的函数f&lt;T&gt;(T)T=int

我们选择f&lt;T&gt;(T)(选择只发生在主模板上) 在专业化f&lt;int&gt;(int) 中解决。

第二种情况:f&lt;int&gt;(123);

现在,这两个功能都是可行的:

  • template&lt;typename T&gt; void f(typename S&lt;T&gt;::type)T=int
  • f&lt;T&gt;(T)T=int

第一个比第二个更专业,所以选择第一个。

注意:
Gcc、Clang 和 Mscv 同意 Demo

【讨论】:

  • 他们不同意。如果您只是切换声明的顺序,结果将会改变。简直就是UB。
  • 其他订单给出相同的结果here。 consexpr 应避免 UB,仅保留格式错误的程序 NDR。
  • 请学习检查自己的声明godbolt.org/z/-ppupY
  • @ALX23z:您不能在通用模板之前编写(完整)专业化。您的版本无法编译(不是因为 static_assert)。
  • 它在 MSVC 上不起作用,因为它会产生链接器错误。这两个函数都生成相同的符号,导致 UB。此外,如果f(123) 产生一个数字而f&lt;int&gt;(123) 产生另一个数字,这已经是一个大问题了。在 VS2019 中,如果同时使用这两个函数,则它们都返回 0,但如果只存在一个,则它们会导致 0 或 3。
猜你喜欢
  • 2017-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多