具有可变参数包的函数的 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) {}
考虑以下代码片段(在 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) {}