5

考虑以下代码片段(可在编译器 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 都失败了(编译器说ambiguous call

为什么 Clang 和 MSVC 无法进行如此看似明显的推论?

编辑:GCC 为我作为用户提供了预期的解决方案,是否有一种简单的方法可以推动 clang 和 msvc 选择模板而无需对原始代码进行太多更改?

4

2 回答 2

6

如果您检查编译器的其他诊断行,您会看到它说

<source>(6): note: could be 'auto foo<char>(char &&)'
<source>(3): note: or       'auto foo<char,char>(char &&)'

(来自 MSVC;Clang 类似)

在这种情况下,由于函数的第一个(唯一)参数foo是 a 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 中的一个错误。

于 2019-07-08T23:37:52.167 回答
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 在其实现中超越了标准,当手动给出模板参数时,它对 #1 的偏好更高,而 Clang 和 MSVC 则保持原样。

此外,只有当可变参数包和 T 中的第一个参数解析为完全相同的类型时,才会出现歧义。

解决方案

这是我为我的用例找到的解决方案。(前向对象构造或可变参数包)

变体 1

声明一个专门针对一个参数的额外函数,这将优先于基于可变参数的函数。(不缩放或概括)

template<typename T> auto foo(T&& args) {}
//or
template<typename T, typename Arg> auto foo(Arg&& arg) {}

变体 2

当非空参数包的第一个参数与给定类型 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) {}
于 2019-07-11T21:48:39.083 回答