1

I wrote a convenience function that gets some arguments and then sends them to the right functions:

void combine(int a, int b, double one, double two) {
    funA(a,b);
    funB(one, two);
}

Now I want to do it again, but with variadic template parameters for both my combine function and the funA-funB pair:

//somewhere earlier in the code 
template<class U, class T> funA(U par1, T par2) {...}
template<class X, class Y> funB(X par1, Y par2) {...}

template<class ...ParamA, class ...ParamB>
void combine(ParamA...packA, ParamB...packB) {
     funA(packA...);
     funB(packB...);
}

This of course can't work because I need some way to divide the parameters list in half. But what is more interesting is when I try to compile the above code with a call like combine(10, 'a', 12.0, true) I get this error:

In instantiation of ‘void combine(ParamA ..., ParamB ...) [with ParamA = {}; ParamB = {int, char, double, bool}]’:
...
error: no matching function for call to ‘funA()’
....
error: no matching function for call to ‘funB(int&, char&, double&, bool&)’

which shows that ParamB "ate" all of the parameter list.

So, my question is: is there a way to divide the parameter list of a function with variable template list in half?. If not, how else may I write my combine function?

Felix.leg
  • 622
  • 1
  • 4
  • 13

1 Answers1

1
#include <tuple>
#include <utility>

#include <iostream>
#include <memory>

//somewhere earlier in the code 
template<class U, class T> void funA(U par1, T par2) {
    std::cout<<par1 <<' '<< par2 <<'\n';
}
// Use move-only type to check forwarding
template<class X> void funB(X par1, std::unique_ptr<int> par2) {
    std::cout<<par1 <<' '<< *par2 <<'\n';
}

// Call fnc with a subset of arguments, more general than we need for the partitioning.
template<typename Fnc,std::size_t...Is, typename...Args>
auto call_sub(Fnc fnc, std::index_sequence<Is...>, Args&&...args){
   
    auto tup = std::forward_as_tuple(std::forward<Args>(args)...);
    // Extract relevant indices.
    return fnc(std::get<Is>(std::move(tup))...);
}

// Shift index sequences
template< std::size_t shift_amount,std::size_t...Is>
constexpr auto shift_sequence( std::index_sequence<Is...>){
    return  std::index_sequence<shift_amount +Is...>{};
}

template<class ...Params>
void combine(Params&&...pack) {
    static_assert(sizeof...(pack) % 2 == 0);
    constexpr std::size_t half = sizeof...(pack) /2;

    constexpr std::make_index_sequence<half> first_half;
    constexpr auto second_half = shift_sequence<half>(first_half);
    // Lambda must be used because funA nor funB are not functions, they are templates which cannot be passed around.
    call_sub([](auto&&...args){return funA(std::forward<decltype(args)>(args)...);},
              first_half, std::forward<Params>(pack)...);
    call_sub([](auto&&...args){return funB(std::forward<decltype(args)>(args)...);},
              second_half, std::forward<Params>(pack)...);


}
int main (int argc, char *argv[])
{
    combine(1,2,3,std::make_unique<int>(10));
}

Output:

1 2
3 10

Live Godbolt demo

Quimby
  • 17,735
  • 4
  • 35
  • 55