为啥接受数组的 C++ 模板并不比接受 GCC 5.3 和 Clang 4.0 的指针更专业?

Posted

技术标签:

【中文标题】为啥接受数组的 C++ 模板并不比接受 GCC 5.3 和 Clang 4.0 的指针更专业?【英文标题】:Why is a C++ template accepting an array not more specialized than one accepting a pointer according to GCC 5.3 and Clang 4.0?为什么接受数组的 C++ 模板并不比接受 GCC 5.3 和 Clang 4.0 的指针更专业? 【发布时间】:2018-07-02 12:33:39 【问题描述】:

为什么接下来的两个模板声明是模棱两可的(所以没有一个比另一个更专业)?我知道这个问题已经在 Stack Overflow 上提出过很多次了,但通常人们会回答如何解决歧义,而不是为什么会发生这种情况。

我。 template <class T> void func(char* buf, T size)

二。 template <std::size_t N> void func(char (&buf)[N], std::size_t size)

尝试通过 C++14 标准的步骤来解决部分函数模板排序 (14.5.6.2):

为每个类型、非类型或模板模板参数(包括 模板参数包(14.5.3))分别合成一个唯一的类型、值或类模板 并将其替换为模板函数类型中该参数的每次出现。

转换后的函数 I 模板的函数类型是:void func(char*, U1),其中U1 是一些独特的合成类型。

转换后的函数II模板的函数类型是:void func(char (&buf)[N1], std::size_t),其中N1是一些唯一的合成值。

使用转换后的函数模板的函数类型,对另一个模板进行类型推导 如 14.8.2.4 中所述。

所以让我们尝试在一侧(使用第一个模板作为参数,第二个作为参数模板)和另一侧执行类型推导。

案例 1。

参数模板:template <std::size_t N> void func(char (&buf)[N], std::size_t size)。 转换后的参数模板:void func(char*, U1)

试图推断模板参数。 “char (&buf)[N]”不能从“char*”类型推导出来。 U1 也不匹配 std::size_t 类型。失败。

案例 2。

参数模板:template <class T> void func(char* buf, T size)。 转换后的参数模板:void func(char (&buf)[N1], std::size_t)

试图推断模板参数。参数模板的第一个参数根本不是类型,它与char[] 兼容。 T 应该推导出为std::size_t

所以模板二应该更专业,应该在下面的代码中选择:

char buf[16];
func(buf, static_cast<std::size_t>(16));

为什么 GCC 5.3 和 Clang 4.0 不是这样?

【问题讨论】:

很抱歉,这个问题写得有点混乱。我不明白 case 1 和 case 2 对应的是什么,也不明白你在每个例子中是如何称呼它的,也不明白为什么 U1 不匹配 std::size_t。更一般地说,我会添加数组到指针的转换非常激进,并且在过去弄清楚这种转换是否真的影响排序是很棘手的。不过,SO 上已经有多个此类问题。 我不确定这是关于偏序的,但可能是一个函数在第一个参数上更匹配,另一个在第二个参数上匹配。 1.函数调用的偏序不使用函数类型;它单独使用参数类型。 2. 已知该领域的规范存在问题。对于这个,尚不清楚在偏序期间是否或在什么情况下检查原始和推导的 A 的身份。如果它们被如此检查,那么P=char *A=char [N1] 将无法扣除。 (也就是说,如果我们忽略 CWG 1391 对 [temp.deduct.partial]p4 的可疑添加,这会导致其自身的问题。) @T.C.那么如果考虑到 CWG 1391,这个 A/P 扣除就不会继续,模板 II 会被认为更专业? 我想知道您期望的答案是什么。如果编译器不符合 C++ 标准,那么答案就是这样。你可以用“为什么 Visual Studio 2006 不符合 C++03”来问同样的原因。顺便说一句,这并不是编译器不兼容的唯一情况。我向 Clang 和 GCC 提交的 100 份律师错误报告中的一些在我提交报告数年后仍然开放。 bugs.llvm.org/… 【参考方案1】:

模板声明没有歧义;以下代码编译运行正常:

#include <iostream>
#include <string>

using namespace std;

template<class T>
void func(char* buf, T size) cout<<"void func(char*,T)\n";
template<size_t N>
void func(char (&buf)[N], std::size_t size) 
  cout<<"void func(char (&)[],size_t)\n";

int main() 
  char buf[3];
  func(buf, 2);
  func<3>(buf, 2);
  func(reinterpret_cast<char (&)[3]>(buf), 2);
  //next is ambiguous
  //func(reinterpret_cast<char (&)[3]>(buf), size_t(2));
  func<3>(reinterpret_cast<char (&)[3]>(buf), size_t(2));
  return 0;

但是,注释掉的调用是模棱两可的。为了消除歧义,请使用:

func<3>(reinterpret_cast<char (&)[3]>(buf), size_t(2));

这可以正常工作并调用正确的函数。

【讨论】:

最初的问题是关于当第二个参数不参与歧义解决时两个模板特化之间的歧义。为了实现它,第二个参数应该是 std::size_t 类型。您的示例没有回答这个问题,也没有证明“模板声明没有歧义”,因为第一个或第二个参数变得更加专业。如果您尝试将模板称为“func(buf, static_cast<:size_t>(2))”,则会遇到原始问题。

以上是关于为啥接受数组的 C++ 模板并不比接受 GCC 5.3 和 Clang 4.0 的指针更专业?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 const 限定变量被接受为 gcc 上的初始化程序?

为啥复制构造函数应该在 C++ 中通过引用来接受它的参数?

重载 [] 和 = 运算符以在 C++ 中接受我的模板类的值

GCC 10.0.1 接受包含不在基本源字符集中的字符的标头名称

为啥我的模板不接受初始化列表

为啥只接受我密钥中的前 2 个字符? C++ [关闭]