具有可变参数包的函数的C ++部分模板参数推导在Clang和MSVC中产生歧义调用

时间:2019-07-08 23:14:43

标签: c++ templates variadic-templates ambiguous-call

请考虑以下代码段(在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选择模板,而无需更改原始代码?

2 个答案:

答案 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中的第一个参数解析为完全相同的类型时,模棱两可才出现。

解决方案

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

变种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) {}