可变参数模板展开为std::tuple

3

我有一个过滤器类,它有两个模板参数:输入数量和输出数量。

template<int Ins, int Outs>
class Filter
{
    // implementation
};

有时我需要将多个过滤器串联起来,所以我想将它们包装在一个类中。
template<int... args>
class Chain
{
};

这样当我使用链时

Chain<5, 10, 25, 15> chain;

在Chain类中,it将args展开为元组,最终得到类似于这样的结果。
std::tuple<Filter<5, 10>, Fiter<10, 25>, Filter<25, 15>> filters;

有类似这样的可能吗?我对这些概念还比较陌生,无法理解。
3个回答

9
我们可以用三行代码来完成这个操作,而且不需要递归:
template<int... args>
struct Chain
{
    // put args... into a constexpr array for indexing
    static constexpr int my_args[] = {args...};

    // undefined helper function that computes the desired type in the return type
    // For Is... = 0, 1, ..., N-2, Filter<my_args[Is], my_args[Is+1]>...
    // expands to Filter<my_args[0], my_args[1]>,
    //            Filter<my_args[1], my_args[2]>, ...,
    //            Filter<my_args[N-2], my_args[N-1]>

    template<size_t... Is>
    static std::tuple<Filter<my_args[Is], my_args[Is+1]>...>
                helper(std::index_sequence<Is...>);

    // and the result
    using tuple_type = decltype(helper(std::make_index_sequence<sizeof...(args) - 1>()));
};

Demo.


好的解决方案,尽管我更喜欢一个更简洁的演示,比如这个 - Marco A.

5
我们可以通过一些递归模板魔法来实现这个目标:
//helper class template which will handle the recursion
template <int... Args>
struct FiltersFor;

//a helper to get the type of concatenating two tuples
template <typename Tuple1, typename Tuple2>
using tuple_cat_t = decltype(std::tuple_cat(std::declval<Tuple1>(),
                                            std::declval<Tuple2>())); 

//pop off two ints from the pack, recurse
template <int Ins, int Outs, int... Others>
struct FiltersFor<Ins,Outs,Others...>
{
    //the type of concatenating a tuple of Filter<Ins,Outs> with the tuple from recursion
    using type = tuple_cat_t<std::tuple<Filter<Ins,Outs>>, 
                             typename FiltersFor<Outs,Others...>::type>;    
};

//base case, 1 int left
template <int Dummy>
struct FiltersFor<Dummy>
{
    using type = std::tuple<>;
};

//for completeness
template <>
struct FiltersFor<>
{
    using type = std::tuple<>;
};

//our front-end struct
template<int... args>
using Chain = typename FiltersFor<args...>::type;

另外,我们可以摆脱单个 int 和无 int 版本,并像这样定义主模板:

template <int... Args>
struct FiltersFor
{
    using type = std::tuple<>;
};

现在我们可以这样测试:
static_assert(std::is_same<Chain<1,2,3,4>, std::tuple<Filter<1,2>,Filter<2,3>,Filter<3,4>>>::value, "wat");
static_assert(std::is_same<Chain<1,2>, std::tuple<Filter<1,2>>>::value, "wat");
static_assert(std::is_same<Chain<>, std::tuple<>>::value, "wat");

演示


1
对于 Chain<1,2,3,4>,输出应该是 std::tuple<Filter<1,2>,Filter<2,3>,Filter<3,4>>,而不是 std::tuple<Filter<1,2>,Filter<3,4>> - Piotr Skotnicki
@buttifulbuttefly 当然可以,但是我认为对不熟悉这些概念的 OP 来说,明确不同情况会更有帮助。不过我会把这个加到我的答案中,谢谢。 - TartanLlama
我最终在这个特定案例中使用了另一种解决方案,但这对于理解模板元编程的思考过程非常有帮助,谢谢! - dominiktomicevic

0

我之前遇到过类似的问题,最终使用了一个接受 Filter<Ins1, Ins> 对象并构建一个 Filter<Ins1, Outs> 的类 Filter 运算符*。

template<int Ins, int Outs>
class Filter
{
    template <int Ins1>
    Filter<Ins1, Outs> operator*(const Filter<Ins1, Ins> &rhs) const
    {
    // implementation
    }
};

现在的问题是:你的过滤器是做什么的?是否可以进行组合(也许我有偏见,因为在我的上下文中,Filter是一个函数,而在我的情况下,operator* 是函数组合)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接