12

假设我们有以下代码:

template <typename T>
void foo(const T&);

int main()
{
   foo("str");
}

示范

gcc 4.7.2、clang 3.2、icc 13.0.1

对 `void foo< char [4]>(char const (&) [4])' 的未定义引用

MSVC-11.0

未解析的外部符号 "void __cdecl foo< char const [4]>(char const (&)[4])" (??$foo@$$BY03$$CBD@@YAXAAY03$$CBD@Z)

注意char[4]第一个输出和char const[4]第二个输出。

为什么?谁是对的?可以引用一下标准吗?

4

2 回答 2

5

海合会是对的。

让我们从一个稍微简单的例子开始,稍后证明原来的例子也遵循同样的模式:

template<typename T>
void bar(T const&)
{
    // Shall not fire
    static_assert(std::is_same<T, int>::value, "Error!");
}

int main()
{
    int x = 0;
    bar(x); // 1 - Assertion won't fire

    int const y = 0;
    bar(y); // 2 - Assertion won't fire
}

这里发生了什么事?首先,根据第 14.8.2.1/3 节:

[...] 如果 P 是引用类型,则 P 引用的类型用于类型推导。[...]

这意味着类型推导将尝试匹配T constint在情况 1 中)和反对int const(在情况 2 中)。在第二种情况下,替换intT产生一个完美的匹配,所以这很容易;在第一种情况下,我们const正在努力实现完美匹配。但这就是第 14.8.2.1/4 节发挥作用的地方:

[...]如果原始 P 是引用类型,则推导的 A(即引用所引用的类型)可以比转换后的 A 更具 cv 限定。 [...]

在这里,替换intforT给了我们一个 deduced int const,它比int(参数的类型x)更具 cv 限定性。但这是可以接受的,因为上面的第 14.8.2.1/4 节,所以即使在这种情况下T也被推断为int

现在让我们处理您的原始示例(稍作调整,但我们最终会使用原始版本):

template<typename T>
void bar(T const&)
{
    // Does not fire in GCC, fires in VC11. Who's right?
    static_assert(std::is_same<T, char[4]>::value, "Error!");
}

int main()
{
    char x[] = "foo";
    bar(x);

    char const y[] = "foo";
    bar(y);
}

除了我替换int为之外char [],这是示例和我的第一个示例在结构上是相同的。要了解为什么这种等价性成立,请考虑下面的断言(正如预期的那样,它不会在任何编译器上触发):

// Does not fire
static_assert(
    std::is_same<
        std::add_const<char [4]>::type,
        char const[4]
    >::value, "Error");

C++11 标准在第 3.9.3/2 段中规定了这种行为:

应用于数组类型的任何 cv 限定符都会影响数组元素类型,而不是数组类型 (8.3.4)。

第 8.3.4/1 段还规定:

[...] 任何形式的“cv-qualifier-seq array of NT”都被调整为“array of N cv-qualifier-seq T”,同样适用于“array of unknown bound of T”。可选的属性说明符序列属于数组。[ 例子:

typedef int A[5], AA[2][3];
typedef const A CA; // type is “array of 5 const int”
typedef const AA CAA; // type is “array of 2 array of 3 const int”

—结束示例] [注意:“N cv-qualifier-seq T 的数组”具有 cv 限定类型;见 3.9.3。——尾注]

由于现在很清楚这两个示例展示了相同的模式,因此应用相同的逻辑是有意义的。这将引导我们走同样的推理路径。

在执行类型推导时,在第一种情况下T const匹配,在第二种情况下匹配。char[4]char const[4]

在第二种情况下,T = char[4]产生完美匹配,因为在替换之后T const变为。char const[4]在第一种情况下, deducedA再次比原始 cv 更合格A,因为它代替char[4]Tyield char const[4]。但话又说回来,这是 14.8.2.1/4 允许的,所以T应该推导出为char[4].

最后,回到你原来的例子。由于字符串字面"str"量也有 type char const[4],T应该推导出为 be char [4], 这意味着GCC 是对的:

template<typename T>
void foo(T const&)
{
    // Shall not fire
    static_assert(std::is_same<T, char[4]>::value, "Error!");
}

int main()
{
    foo("str"); // Shall not trigger the assertion
}
于 2013-03-19T16:15:37.907 回答
1
于 2013-03-19T14:35:37.773 回答