使用默认函数模板参数的意外重载解析
Unexpected overload resolution with default function template parameter
我正在经历一个似乎非常意外的重载解析行为。下面的代码会被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现在都接受代码,并选择我最初期望的第一个重载。
我看不出这个改变会对行为产生什么影响:我所做的只是用默认值(T
)代替既没有明确指定也没有推导的模板参数(U
)的使用。
然后,改变之前的行为是意想不到的,所以也许我错过了什么。
谁能解释一下:
- 为什么第一种情况是模棱两可的;和
- 为什么我所做的更改解决了歧义?
如果它是相关的,我测试的编译器版本是gcc 4.8.0和clang的最新主干构建。
问题是,在实参推导之后,是否存在将推导出来的模板实参替换到形参列表中的阶段。在此阶段,默认实参将用于尚未推导出的模板形参。
这个额外的步骤是在什么情况下执行的,而不是在什么情况下执行的问题是一个活跃的核心问题,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
,因此第一个模板没有被认为比第二个模板"更专业"。
- 继承函数的重载解析
- 你能重载对象变量名本身返回的内容吗
- 从父命名空间重载类型
- 使用C++中的模板和运算符重载执行矩阵运算
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 重载操作程序时出错>>用于类中的字符串 memebr
- 一个关于在C++中重载布尔运算符的问题
- 不同翻译单元中不可重载的非内联函数定义
- 为什么使用SFINAE而不是函数重载
- 在C++中对T*类型执行std::move的意外行为
- 为什么我不能在 C++ 中的特定函数重载中调用同一函数的任何其他重载?
- 将重载的成员函数传递给函数模板
- c++:可变模板和函数重载
- 在gcc中意外调用了Const重载.编译器错误或兼容性修复程序
- 子类调用意外的重载函数
- C++函数重载中的意外行为
- 视觉工作室中涉及 void*、字符串和常量字符 [] 的意外重载解决方案
- 将std::initializer_list与布尔重载函数一起使用时,重载解析出现意外行为
- C++ 具有特征的瓦拉迪试时,带有参数列表的重载函数会产生意外的结果
- 使用默认函数模板参数的意外重载解析