首页 > 解决方案 > 将可变参数分成两半

问题描述

我编写了一个便利函数,它获取一些参数,然后将它们发送到正确的函数:

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

现在我想再做一次,但我的combine函数和funA-funB对都使用可变模板参数:

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

这当然行不通,因为我需要一些方法将参数列表分成两半。但更有趣的是,当我尝试通过调用来编译上述代码时, combine(10, 'a', 12.0, true)我得到了这个错误:

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&)’

这表明ParamB“吃”了所有参数列表。

所以,我的问题是:有没有办法将带有变量模板列表的函数的参数列表分成两半?. 如果没有,我还能如何编写我的combine函数?

标签: c++variadic-templates

解决方案


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

输出:

1 2
3 10

现场 Godbolt 演示


推荐阅读