简体   繁体   中英

multiple parameter pack expansion by gcc and clang

I am trying to use variadics to convert N parameters function to 2^N parameters function. The following snippet compiles happily by clang 3.9 , while nvcc 8.0 (effectively gcc 5.4 ) fails miserably with the error:

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

code:

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

called like

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

    return 0;
}

Am I doing something wrong? How do I make it work with gcc ?

This code runs afoul of [temp.deduct.type]:

The non-deduced contexts are: [...] A function parameter pack that does not occur at the end of the parameter-declaration-list .

as in:

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

and [temp.arg.explicit]:

A trailing template parameter pack (14.5.3) not otherwise deduced will be deduced to an empty sequence of template arguments.

This deduce-nondeduced-packs-as-empty idea breaks your code on both gcc and clang in different ways. Consider the call apply(1,2) :

  • every version of gcc I've tried considers the Ds... ds pack empty and deduces Ds... as <int> and Is... as <int, unsigned int> . So that overload is thrown out as it takes 5 arguments and we're only passing 4.
  • every version of clang I've tried deduces the second Ds... as <int> from the first argument and <> from the pack and consider the deduction to be a failure due to the inconsistency.

Either way, there's not really a path forward here.


What you can do instead is to flip the order and put all the Is... first. Since you know all the types, you can explicitly specify them, and let the Ds... be deduced. That is:

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

This works on every compiler.

So I played with the code a bit and came up with 3 versions:

Compiles in clang, fails with 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);
}

Interesting, both typelist are required for this to work, although it seems that just one would be enough to resolve the ambiguity.

Next one is from Barry's answer. It compiles with gcc, but fails with clang for me:

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

And finally the one working with both gcc(5.4, 6.3) and 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);
}

Hope somebody finds this helpful.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM