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

C++ partial template argument deduction for function with variadic pack produces ambiguous call in Clang and MSVC

考虑以下代码片段(在 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 选择模板而无需对原始代码进行太多更改?

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

<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);

它将编译。

语言规范("Partial ordering of function templates"、[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的一个bug。

经过一些测试,并使用提到的标准参考:[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

声明一个专门用于一个参数的额外函数,这将优先于 variadic-based 那些。 (不缩放或概括)

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

变体 2

当 non-empty 参数包的第一个参数与给定类型 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) {}