为什么指针衰减优先于推导出的模板?

Bar*_*rry 51 c++ arrays overload-resolution

假设我正在编写一个函数来打印字符串的长度:

template <size_t N>
void foo(const char (&s)[N]) {
    std::cout << "array, size=" << N-1 << std::endl;
}

foo("hello") // prints array, size=5
Run Code Online (Sandbox Code Playgroud)

现在我想扩展foo以支持数组:

void foo(const char* s) {
    std::cout << "raw, size=" << strlen(s) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

但事实证明,这破坏了我原来的预期用途:

foo("hello") // now prints raw, size=5
Run Code Online (Sandbox Code Playgroud)

为什么?这不需要数组到指针的转换,而模板是完全匹配的吗?有没有办法确保我的数组函数被调用?

Col*_*mbo 43

这种(符合标准的)歧义的根本原因似乎在于转换成本:过载分辨率试图最小化将参数转换为相应参数所执行的操作.数组实际上是指向其第一个元素的指针,但是带有一些编译时类型信息.数组到指针的转换成本不会超过例如保存数组本身的地址或初始化对它的引用.从这个角度来看,模糊性似乎是合理的,虽然从概念上讲它是不直观的(并且可能是次要的).实际上,这个论证适用于所有Lvalue转换,如下面的引用所示.另一个例子:

void g() {}

void f(void(*)()) {}
void f(void(&)()) {}

int main() {
    f(g); // Ambiguous
}
Run Code Online (Sandbox Code Playgroud)

以下是强制性标准.不是某些函数模板的特化的函数优于那些两者都是同等匹配的函数(参见[over.match.best] /(1.3),(1.6)).在我们的例子中,执行的转换是一个数组到指针的转换,它是具有精确匹配等级的左值变换(根据[over.ics.user]中的表12).[over.ics.rank]/3:

  • 标准转换序列S1比标准转换序列更好的转换序列 S2,如果

    • S1是一个适当的子S2序列(比较13.3.3.1.1定义的规范形式的转换序列,不包括任何左值变换 ;身份转换序列被认为是任何非同一性转换序列的子序列),或者,如果不是,

    • 军衔S1比的排名更好S2,或S1S2具有相同的等级,并在下面的段落中的规则区分,或者,如果不说,

    • [..]

第一个要点不包括我们的转换(因为它是左值变换).第二个需要差异,这是不存在的,因为两个转换都具有完全匹配等级; "下面段落中的规则",即在[over.ics.rank]/4中,也不包括数组到指针的转换.
所以信不信由你,两个转换序列都没有比另一个好,因此选择了char const*-overload.


可能的解决方法:将第二个重载定义为函数模板,然后启动部分排序并选择第一个重载.

template <typename T>
auto foo(T s)
  -> std::enable_if_t<std::is_convertible<T, char const*>{}>
{
    std::cout << "raw, size=" << std::strlen(s) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

演示.

  • @AaronMcDaid不.数组到指针的转换是标准转换序列,而构造函数是用户定义的转换序列.后者只允许一次,但前者也可以与之结合使用.:) (2认同)