我遇到了一个看起来很意外的重载解析行为。以下代码被gcc和clang拒绝,并出现歧义错误:
template <typename T>
struct A
{
typedef T key_type;
};
template <typename T>
void foo(A<T> rng, T val);
template <typename T, typename U = T>
void foo(T, typename U::key_type);
int main()
{
A<int> i;
foo(i, 0);
}
错误是:
test.cpp:16:5: error: call to 'foo' is ambiguous
foo(i, 0);
^~~
test.cpp:8:6: note: candidate function [with T = int]
void foo(A<T> rng, T val);
^
test.cpp:11:6: note: candidate function [with T = A<int>, U = A<int>]
void foo(T, typename U::key_type);
^
我希望两者都是完全匹配,但是在部分排序中获胜的第一个重载,因为在第一个参数A<T>
中比T
更专业。
让我感到震惊的是,如果我将第二个签名更改为:
template <typename T, typename U = T>
void foo(T, typename T::key_type);
gcc和clang现在都接受代码,并按照我原先的预期选择第一个重载。
我没有看到这种变化如何影响行为:我所做的只是替代使用模板参数,该模板参数既未明确指定也未推断(U
)及其默认值({{ 1}})。
然后,改变之前的行为开始意外,所以也许我错过了一些东西。
有人可以解释一下:
如果它是相关的,我测试的编译器版本是gcc 4.8.0和最近的clang树干版本。
答案 0 :(得分:2)
问题是在推论参数后,推导出的模板参数的替换阶段是否存在于参数列表中。此阶段是默认参数将用于尚未推断的模板参数的位置。
这个额外步骤的推论背景是什么,以及什么不是活跃的核心问题,http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#697。
如果你做了额外的替换步骤,你还需要实例化模板(否则替换步骤本身就没有多大意义)。你也可以只选择默认参数,而不进行替换,但是在标准中这两个东西是一起的,所以作为实现者我不会选择那个路径。
部分排序在很大程度上独立于正在进行部分排序的上下文(考虑了一些与上下文相关的事物 - 例如,忽略没有显式调用参数的函数参数)。它也与模板参数是否传递了显式模板参数无关(所以如果你给了U
一个值,部分排序就不会“记住”它。
Clang和GCC不执行替换步骤,也不使用模板默认参数。因此,当T
与U::key_type
进行比较以找出U
时,他们会说“嗯,一个非演绎的背景。我们会说'成功,没有什么不匹配!'对于此参数“。将T
与T::key_type
进行比较时会发生同样的情况。当它将WhatEver::key_type
与T
对比时,T
也可以推断出该依赖类型。因此,对于第二个参数,在两次尝试中,两个模板至少都是彼此特殊的。
然而,重要的区别是扣除后,必须有参数类型列表中使用的所有参数的值。
在大多数情况下,所有模板参数都必须具有值以便推断成功,但是对于部分排序目的,模板参数可以保持不带值,前提是它不用于用于部分排序的类型。 [注意:使用非推断上下文中使用的模板参数。 - 结束说明]
在第二次尝试中,T
是由第一个参数推断出来的,所以在完成参数/参数类型的比较后没有发生任何不好的事情。在第一次尝试中,U
未被保留,因此第一个模板在后果中被认为不是“更专业”。