【问题标题】:Why does pointer decay take priority over a deduced template?为什么指针衰减优先于推导的模板?
【发布时间】:2015-03-30 08:53:20
【问题描述】:

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

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

foo("hello") // prints array, size=5

现在我想扩展foo 以支持-arrays:

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

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

foo("hello") // now prints raw, size=5

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

【问题讨论】:

  • foo&lt;&gt;("hello"); 将调用模板,如果有帮助的话。我认为这是拒绝非模板的简单方法。并强制使用非模板使用(&amp;foo)("hello") - 您可以获取非模板的地址,但不能获取模板的地址。
  • @LanguageLawyer 没有?这个问题的 why 部分非常突出——链接的问题没有问为什么,答案也没有回答为什么。这个答案可以。它们是相关的,但不是重复的。

标签: c++ arrays overload-resolution


【解决方案1】:

这种(符合标准的)歧义的根本原因似乎在于转换成本:重载解析试图最小化将参数转换为相应参数所执行的操作。一个数组实际上是指向它的第一个元素的指针,并装饰有一些编译时类型信息。数组到指针的转换不会花费更多,例如保存数组本身的地址,或初始化对它的引用。从这个角度来看,这种模棱两可似乎是有道理的,尽管在概念上它是不直观的(并且可能低于标准)。事实上,这个论点适用于所有左值变换,正如下面的引用所暗示的那样。另一个例子:

void g() {}

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

int main() {
    f(g); // Ambiguous
}

以下是强制性标准。不是某些函数模板的特化的函数比那些在其他方面都同样好的匹配的函数更受欢迎(参见 [over.match.best]/(1.3), (1.6))。在我们的例子中,执行的转换是数组到指针的转换,它是具有精确匹配等级的左值转换(根据 [over.ics.user] 中的表 12)。 [over.ics.rank]/3:

  • 标准转换序列S1 是比标准转换序列S2 if 更好的转换序列

    • S1S2 的适当子序列(比较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;
}

Demo.

【讨论】:

  • 想象一个函数bar,它接受一个X类型的对象。此外,X 只有一个以const char * 作为参数的构造函数。您可以调用bar("hello");,即使这看起来您正在进行两次转换,数组到指针和指针到X,而通常只允许一次隐式转换。我想我们“免费”获得了数组到指针的衰减。这是一种可以接受的表达方式吗?
  • @AaronMcDaid 不。数组到指针的转换是标准转换序列,而构造函数是用户定义的转换序列。后者只允许出现一次,但前者也可以与它结合出现。 :)
  • *调用的转换构造函数是用户定义的转换序列的一部分。一个用户定义的转换序列由一个用户定义的转换(ctor 调用)加上一个初始的标准转换序列(数组到指针)加上一个最终的标准转换序列(这里是标识)组成。当然不是序列本身。
猜你喜欢
  • 2016-02-17
  • 1970-01-01
  • 2021-12-01
相关资源
最近更新 更多