1

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?

Slava
  • 1,528
  • 1
  • 15
  • 23
  • 1
    What's the full call that you're making? Shouldn't compile at all. – Barry Jan 30 '17 at 15:58
  • gcc 6.3.1 does not have a problem with this. – Sam Varshavchik Jan 30 '17 at 15:59
  • @SamVarshavchik Really? It has a problem with this for me. Did you pass at least two args into `apply()`? – Barry Jan 30 '17 at 16:23
  • Yes. I checked it with two arguments and gcc 5.4. Although I stripped the code a bit for SO, will check once again. – Slava Jan 30 '17 at 16:24
  • @Barry - I assumed this was an MCVE. Obviously, it is not. Adding an explicit call revealed the problem. I see where the problem is. – Sam Varshavchik Jan 30 '17 at 17:20
  • `void _apply(Typelist , D0 d0, Ds... ds, Is... is)` -- you have a non-last parameter pack. This function call cannot be deduced. See the duplicate question for more information. – Sam Varshavchik Jan 30 '17 at 17:21
  • @SamVarshavchik No shit, maybe you should try that next time before posting that there's no problem? – Barry Jan 30 '17 at 17:30
  • Sorry, that is all my fault, I changed the code too much before posting and did not check it actually compiles. And then the thing which compiled did so with clang 4.8, not gcc... I screwed a lot of things here, so it is really great that Barry was able to provide the answer without seeing the actual problem. :) – Slava Jan 30 '17 at 17:35

2 Answers2

2

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
0

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.

Slava
  • 1,528
  • 1
  • 15
  • 23