利用gcc和clang进行多参数包扩展

multiple parameter pack expansion by gcc and clang

本文关键字:包扩展 扩展 参数 gcc clang 利用      更新时间:2023-10-16

我正在尝试使用变量将N参数函数转换为2^N参数函数。下面的代码段由clang 3.9编译得很好,而nvcc 8.0(实际上是gcc 5.4)则失败得很惨,并出现错误:

error: no instance of overloaded function "foo" matches the argument list

代码:

template<class... Ts> struct Typelist{};
template <class..., class... Us>
void foo(Typelist<>, Typelist<Us...>, Us... us ){
//  do the actual work
}
template <class T, class... Ts, class... Us>
void foo(Typelist<T, Ts...>, Typelist<Us...>, T t, Ts... ts, Us... us){
foo(Typelist<Ts...>{}, Typelist<Us..., Us...>{}
, ts..., us..., (us+t)...);
}
template <class... Ts>
void bar(Ts... ts){
foo(Typelist<Ts...>{}, Typelist<unsigned>{}
, ts..., 0u);
}

称为类似

int main(int /*argc*/, char */*argv*/[])
{
bar(2u);
bar(2u, 3u, 4u);
return 0;
}

我做错什么了吗?如何使用gcc

此代码与[temp.dexeract.type]冲突运行:

非推导上下文为:[…]不出现在参数声明列表末尾的函数参数包。

如:

template<class D0, class... Ds, class... Is>
HOST_DEVICE void _apply(Typelist<D0, Ds...> , D0 d0, Ds... ds, Is... is) {
//                                                   ~~~~~~~~

和[temp.arg.extical]:

未以其他方式推导的尾随模板参数包(14.5.3)将被推导为模板参数的空序列。

这将非精简包推断为空思想,以不同的方式破坏了gcc和clang上的代码。考虑调用apply(1,2):

  • 我尝试过的每个版本的gcc都认为Ds... ds包是空的,并将Ds...推导为<int>,将Is...推导为<int, unsigned int>。因此,重载被抛出,因为它需要5个参数,而我们只传递了4个
  • 我尝试过的clang的每个版本都从第一个参数中将第二个Ds...推导出为<int>,从包中将<>推导出,并认为由于不一致性,该推导出失败

不管怎样,这里都没有真正的前进道路。


您可以做的是颠倒顺序,将所有Is...放在第一位。由于您知道所有类型,因此可以显式指定它们,并让Ds...被推导出来。即:

template<class... Is>
HOST_DEVICE void _apply(Is..., Typelist<>)
{
// ...
}
template<class... Is, class D0, class... Ds>
HOST_DEVICE void _apply(Is... is, Typelist<D0, Ds...> , D0 d0, Ds... ds) {
_apply<Is..., decltype(std::declval<Is>()+std::declval<D0>())...>(
is...,
(is+d0)...,
Typelist<Ds...>{},
ds...);
}
template<class... Ds>
HOST_DEVICE void apply(Ds... ds) {
_apply<unsigned int>(0u, Typelist<Ds...>{}, ds...);
}

这适用于每个编译器。

所以我对代码进行了一些处理,并提出了3个版本:

在clang中编译,使用gcc:失败

template <class..., class... Us>
void foo(Typelist<>, Typelist<Us...>, Us... us ){ /*do the stuff*/ }
template <class T, class... Ts, class... Us>
void foo(Typelist<T, Ts...>, Typelist<Us...>, T t, Ts... ts, Us... us){
foo(Typelist<Ts...>{}, Typelist<Us..., Us...>{}, ts..., us..., (us+t)...);
}
template <class... Ts>
void bar(Ts... ts){
foo(Typelist<Ts...>{}, Typelist<unsigned>{}, ts..., 0u);
}

有趣的是,这两种类型列表都是工作所必需的,尽管似乎只有一种就足以解决歧义。

下一个是巴里的回答。它使用gcc进行编译,但对我来说,失败的原因是clang:

template<class... Is>
void foo(Is..., Typelist<>) { /*do the stuff*/ }
template<class... Is, class D0, class... Ds>
void foo(Is... is, Typelist<D0, Ds...> , D0 d0, Ds... ds) {
foo<Is..., decltype(std::declval<Is>()+std::declval<D0>())...>(
is...,
(is+d0)...,
Typelist<Ds...>{},
ds...);
}
template<class... Ds>
void bar(Ds... ds) {
foo<unsigned int>(0u, Typelist<Ds...>{}, ds...);
}

最后是一个同时使用gcc(5.4,6.3)和clang(3.9)的:

template<class... Us>
struct foo_impl<Typelist<>, Typelist<Us...>>{
auto operator()(Us... us)-> void { /*do the stuff here*/ }
};
template<class T, class... Ts, class... Us>
struct foo_impl<Typelist<T, Ts...>, Typelist<Us...>>{
auto operator()(T t, Ts... ts, Us... us)-> void{
foo_impl<Typelist<Ts...>, Typelist<Us..., Us...>>{}(ts..., us..., (us+t)...);
}
};
template <class... Ts>
void bar(Ts... ts){
foo_impl<Typelist<Ts...>, Typelist<unsigned>>{}(ts..., 0u);
}

希望有人觉得这很有帮助。