Mustang
Mustang

Reputation: 417

How do I write a variadic template function in C++ where the parameter pack is not the last parameter?

I'm trying to use a variadic template function where the parameter pack is not the last parameter in the list. Note that there are two recursive calls--one dropping a parameter in front of the pack, the other call dropping a parameter after the pack.

There's no point in using ... if the call site for Blender can't be clean. I could just expand several overloads of Blender myself in that case. I'd really rather not resort to this. I hope I'm just missing something.

int Blender( double t, int i)
{
    return i;
}

template <typename ...Args>
  int Blender( double t, int first, Args... more, int last)
{
    return (1-t)*Blender(t, first, more...) + t*Blender(t, more..., last);
}

static void tryit()
{
    Blender(.5, 23, 42, 89); //doesn't compile
}

Upvotes: 2

Views: 192

Answers (3)

Ben Voigt
Ben Voigt

Reputation: 283684

This is actually really easy once you realize that the limitation is in the type pack deduction and not in the function call. We just need a way to use the type pack we can deduce and pass it explicitly avoiding deduction at the function that needs to pull off the last parameter:

int Blender( double t, int i)
{
    return i;
}

template <typename ...Args>
  int Blender( double t, int first, Args... more);

template <typename ...Args>
  int BlenderWithoutLast( double t, Args... more, int last)
{
    return Blender(t, more...);
}

template <typename ...Args>
  int Blender( double t, int first, Args... more)
{
    return (1-t)*BlenderWithoutLast<Args...>(t, first, more...) + t*Blender(t, more...);
    //    all the magic happens here ^^^^^
}

and now your test case compiles and runs

#include <iostream>
int main()
{
    std::cout << Blender(.5, 23, 42, 89);
}

For me this works with clang and --std=c++11

Upvotes: -1

max66
max66

Reputation: 66200

I propose another solution, really different from the code in the question, that I think is a lot more efficient (creation on only one std::array; blender obtained by template index sequence)

#include <array>
#include <utility>
#include <iostream>

template <typename T, std::size_t I0>
int blenderH (double t, T const & arr, std::index_sequence<I0> const &)
 { return arr[I0]; }

template <typename T, std::size_t I0, std::size_t ... Is>
auto blenderH (double t, T const & arr, 
               std::index_sequence<I0, Is...> const &)
   -> std::enable_if_t<0U != sizeof...(Is), int>
 { return   (1-t) * blenderH(t, arr, std::index_sequence<(Is-1U)...>{})
          +    t  * blenderH(t, arr, std::index_sequence<Is...>{}); }

template <typename ... Args>
int blender (double t, Args ... as)
 {
   static constexpr auto size = sizeof...(Args);

   return blenderH(t, std::array<int, size>{ { as... } },
                   std::make_index_sequence<size>{});
 }

int main()
 { std::cout << blender(.3, 23, 42, 89) << std::endl; }

Unfortunately also this solution works (std::index_sequence and std::make_index_sequence) starting from C++14.

-- EDIT --

Caleth say.

Some explanation of what is going on here would help. I am confused by the lack of I0 in the body of the second overload.

I try an explanation.

Suppose is called the recursive version of BlenderH() (the second overload) with a list of index in the std::index_sequence value. Say 5, 6, 7 and 8; so I0 is 5 and Is... is 6, 7, 8.

We have to recursive call blenderH() with indexes 5, 6, 7 first and with 6, 7, 8 next.

We can avoid to use I0 (5) because

  • 5, 6, 7 is obtained from 6, 7, 8 reducing by 1 every value (so the std::index_sequence<(Is-1U)...> for the first recursive call)

  • and 6, 7, 8 is Is... without modifications (so the std::index_sequence<Is...> in the second one.

From the practical point of view, I0 is declared only to be discarded; ther is no need to use it.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275405

template<std::size_t I>
using count = std::integral_constant<std::size_t, I>;

namespace details {

  template<class...Args, std::size_t N,
    typename std::enable_if<sizeof...(Args)==N, bool>::type = true
  >
  int Blender( count<N> drop, double t, int i, Args...args ) {
    return i;
  }
  template<class...Args, std::size_t N,
    typename std::enable_if<sizeof...(Args)!=N, bool>::type = true
  >
  int Blender( count<N> drop, double t, int i, Args...args ) {
    return (1-t)*Blender( count<N+1>{}, t, i, args... ) + t*Blender( count<N>{}, t, args... );
  }
}

template <typename ...Args>
int Blender( double t, int first, Args... more)
{
  return details::Blender( count<0>{}, first, more... );
}


static void tryit()
{
  Blender(.5, 23, 42, 89); //doesn't compile
}

here count<N> counts the number of arguments at the end to ignore.

The two details overload cover the case where N is equal to the number of arguments in the pack (and hence we have 1 argument left), and when it is not. They are dispatched to using SFINAE.

Upvotes: -1

Related Questions