请考虑以下代码段(在compiler epxlorer上可用):
template<typename T, typename... Args>
auto foo(Args&&... args) {}
template<typename... Args>
auto foo(Args&&... args) {}
int main() {
foo<char>('a');
}
对于GCC来说,它的编译非常好,对于Clang和MSVC而言,它都失败了(编译器说歧义调用)
为什么Clang和MSVC无法如此看似明显的演绎?
编辑:GCC为用户提供了预期的解决方案,是否有一种简便的方法来推动clang和msvc选择模板,而无需更改原始代码?
答案 0 :(得分:6)
如果您检查编译器中的其他诊断行,您会看到它说
<source>(6): note: could be 'auto foo<char>(char &&)'
<source>(3): note: or 'auto foo<char,char>(char &&)'
(来自MSVC; Clang类似)
在这种情况下,由于函数foo
的第一个(唯一)参数是char
,因此编译器无法区分一个模板参数和该模板的两个模板参数版本。
如果将函数调用更改为
foo<char>(10);
它将编译。
语言规范中有一个示例(“功能模板的部分排序”,[temp.func.order]
)与您的代码非常相似:
template<class T, class... U> void f(T, U...); // #1
template<class T > void f(T); // #2
void h(int i) {
f(&i); // error: ambiguous
}
由于GCC进行了编译,因此这是GCC中的错误。
答案 1 :(得分:1)
经过一些测试,并使用提到的对标准的引用:[temp.func.order],[temp.deduct.partial],我对情况有了以下理解。
考虑问题中给出的示例:
template<typename T, typename... Args> auto foo(Args&&... args) {} //#1
template<typename... Args> auto foo(Args&&... args) {} //#2
#2是带有可变参数包的函数,可以推导该参数包。可以推断出 ,而不必推断出 。因此,没有什么可以阻止用户明确指定模板参数。
因此,foo<char>('a')
可能是#2的显式实例与#1的实例一样多,从而引起歧义。该标准不支持过载#1和#2之间的首选匹配。
GCC超出了其实现范围内的标准,因为当手动指定模板参数时C#和MSVC使其保持不变,从而为#1赋予了更高的优先级。
此外,仅当可变参数包和T中的第一个参数解析为完全相同的类型时,模棱两可才出现。
这是我为用例找到的解决方案。 (转发对象构造或可变的对象包)
声明一个专门用于一个参数的额外函数,它将优先于基于可变参数的参数。 (不缩放或概括)
template<typename T> auto foo(T&& args) {}
//or
template<typename T, typename Arg> auto foo(Arg&& arg) {}
当非空参数包的第一个参数与给定类型T相同时,禁用重载。
template<typename T, typename... Args>
constexpr bool is_valid() {
if constexpr(sizeof...(Args)==0)
return true;
else
return !std::is_same_v<T,std::tuple_element_t<0,std::tuple<Args...> > > ;
}
template<typename T, typename... Args, typename = std::enable_if_t<is_valid<T,Args...>()> >
auto foo(Args&&... args) {}