如何任意排序元组的类型?

How can I arbitrarily sort a tuple's types?

关于 C++,真正让我烦恼的一件事是空 struct/class 占用 space.

所以,我认为 std::tuple(或某些变体,因为它(和编译器的)实现高度依赖于实现)可能能够挽救局面,它确实做到了,但是由于包装和对齐存在问题。由于编译器将如何对齐 struct 中的项目,在非空旁边有一个空的旁边有一个空的旁边有一个空的旁边有一个空的旁边有一个非空的旁边将大于 2 个空的旁边有 2 个非空的。

因此,我需要一种方法来根据某些条件对类型重新排序。不需要根据大小对整个列表进行排序(在某些情况下可能是有害的)所以我需要一些通用的方法来重新排序元组的类型列表,但仍然可以访问它,就好像类型列表是原始顺序一样。

我环顾四周,没有发现这样的东西,我很茫然。关于如何实现这一点的想法?

例子

struct A{};
struct B{};
 // Need to be reordered based on some criteria.
std::tuple<A, int, B, float> x;
// In this case move all of the empty objects together like:
//   std::tuple<A, B, int, float> x;
// but still have get<1>(x) return the `int` and get<2>(x) return `B`.
static_assert(std::is_same<decltype(get<0>()), A>::value,     "0 should be type A");
static_assert(std::is_same<decltype(get<1>()), int>::value,   "1 should be type int");
static_assert(std::is_same<decltype(get<2>()), B>::value,     "2 should be type float");
static_assert(std::is_same<decltype(get<3>()), float>::value, "3 should be type B");

无法手动完成的原因是这可能是模板的一部分,元组中的元素可能为空或不为空,具体取决于参数:

template <typename A, typename B, typename C, typename D>
class X
{
    // Need to have this auto arranged given some criteria
    // like size or move all of the empties together.
    tuple<A, B, C, D> x;
  public:
    template<int i>
    auto get() -> typename std::tuple_element<i, decltype(x)>
    {
      return get<i>(x);
    }
};
// What are these types?  Who knows.  This could be buried in some
// template library somewhere.
X<T1, T2, T3, T4> x;

首先,让我们从基础开始。我们需要一种方法将模板模板 (std::tuple) 转换为元函数 class:

template <template <typename...> class Cls>
struct quote {
    template <typename... Args>
    using apply = Cls<Args...>;
};

还有一个类型列表:

template <typename... Args>
struct typelist { };

还有他们之间的事情:

template <typename F, typename TL>
struct apply;

template <typename F, typename... Args>
struct apply<F, typelist<Args...>> {
    using type = typename F::template apply<Args...>;
};

template <typename F, typename TL>
using apply_t = typename apply<F, TL>::type;

所以给定一些类型列表,我们可以有:

using my_tuple = apply_t<quote<std::tuple>, some_typelist>;

现在,我们只需要一个符合某些标准的分区器:

template <typename TL, typename F>
struct partition {
    using type = concat_t<filter_t<TL, F>,
                          filter_t<TL, not_t<F>>
                          >;
};

其中 concat:

template <typename... Args>
struct concat;

template <typename... Args>
using concat_t = typename concat<Args...>::type;

template <typename... A1, typename... A2, typename... Args>
struct concat<typelist<A1...>, typelist<A2...>, Args...> {
    using type = concat_t<typelist<A1..., A2...>, Args...>;
};

template <typename TL>
struct concat<TL> {
    using type = TL;
};

filter:

template <typename TL, typename F>
struct filter;

template <typename TL, typename F>
using filter_t = typename filter<TL, F>::type;

template <typename F>
struct filter<typelist<>, F> {
    using type = typelist<>;
};

template <typename A, typename... Args, typename F>
struct filter<typelist<A, Args...>, F> {
    using type = concat_t<
                     std::conditional_t<F::template apply<A>::value,
                                        typelist<A>,
                                        typelist<>>,
                     filter_t<typelist<Args...>, F>
                     >;
};

not_

template <typename F>
struct not_ {
    template <typename Arg>
    using apply = std::conditional_t<F::template apply<Args>::value,
                                     std::false_type,
                                     std::true_type>;
};

其中,给定 some_typelist 您要放入 tuple 的类型变为:

using my_tuple = apply_t<
    quote<std::tuple>, 
    partition_t<
        some_typelist,
        some_criteria_metafunc_class
    >>;

以 Barry 所做的为基础。

So from here I'd need a mapping meta-function to use the original indices, how would I do that?

首先,一些帮助索引映射的助手。又因为我比较懒,稍微修改了一下typelist

template <typename... Args>
struct typelist { 
    static constexpr std::size_t size = sizeof...(Args);
};

template<class T, std::size_t OldIndex, std::size_t NewIndex>
struct index_map_leaf {
    using type = T;
    static constexpr std::size_t old_index = OldIndex;
    static constexpr std::size_t new_index = NewIndex;
};

template<class... Leaves>
struct index_map : Leaves... {};

给定正确构建的 index_map,从旧索引转换为新索引很简单,利用模板参数推导和重载解析:

template<std::size_t OldIndex, std::size_t NewIndex, class T>
index_map_leaf<T, OldIndex, NewIndex> 
    do_convert_index(index_map_leaf<T, OldIndex, NewIndex>);

template<std::size_t OldIndex, class IndexMap>
using converted_index_t = decltype(do_convert_index<OldIndex>(IndexMap()));

converted_index_t<OldIndex, IndexMap>::new_index 是新索引。

要构建索引图,我们分三步进行。我们首先将类型转换为类型索引对。

template<class... Ts, std::size_t... Is>
typelist<index_map_leaf<Ts, Is, 0>...>
    do_build_old_indices(typelist<Ts...>, std::index_sequence<Is...>);

template<class TL>
using build_old_indices =
    decltype(do_build_old_indices(TL(),  std::make_index_sequence<TL::size>()));

接下来,我们对这些对进行划分。我们需要一个将另一个元函数应用于其参数的嵌套 typedef type 而不是参数本身的元函数。

// Given a metafunction, returns a metafunction that applies the metafunction to
// its arguments' nested typedef type.
template<class F>
struct project_type {
    template<class... Args>
    using apply = typename F::template apply<typename Args::type...>;
};

鉴于此,划分 index_map_leaftypelist 只是 partition_t<LeafList, project_type<F>>

最后,我们转换分区列表,添加新索引。

template<class... Ts, std::size_t... Is, std::size_t...Js>
typelist<index_map_leaf<Ts, Is, Js>...> 
    do_build_new_indices(typelist<index_map_leaf<Ts, Is, 0>...>,
                         std::index_sequence<Js...>);

template<class TL>
using build_new_indices =
   decltype(do_build_new_indices(TL(), std::make_index_sequence<TL::size>()));

把它们放在一起,

template<class TL, class F>
using make_index_map = 
    apply_t<quote<index_map>, build_new_indices<partition_t<build_old_indices<TL>, 
                                                            project_type<F>>>>;

使用一点实用程序将任意模板的参数转换为类型列表:

template<template<class...> class T, class... Args>
typelist<Args...> do_as_typelist(typelist<T<Args...>>);

template<class T>
using as_typelist = decltype(do_as_typelist(typelist<T>()));

我们可以通过直接从 index_map.

构造重新排序的元组类型来仅进行一次分区
template<class Tuple, class F>
struct tuple_partitioner {
    using map_type = make_index_map<as_typelist<Tuple>, F>;
    using reordered_tuple_type = apply_t<project_type<quote<std::tuple>>,
                                         as_typelist<map_type>>;
    template<std::size_t OldIndex>
    using new_index_for = 
        std::integral_constant<std::size_t,
                               converted_index_t<OldIndex, map_type>::new_index>;
};

例如给定

using original_tuple = std::tuple<int, double, long, float, short>;
using f = quote<std::is_integral>;
using partitioner = tuple_partitioner<original_tuple, f>;

以下断言成立:

static_assert(partitioner::new_index_for<0>() == 0, "!");
static_assert(partitioner::new_index_for<1>() == 3, "!");
static_assert(partitioner::new_index_for<2>() == 1, "!");
static_assert(partitioner::new_index_for<3>() == 4, "!");
static_assert(partitioner::new_index_for<4>() == 2, "!");
static_assert(std::is_same<partitioner::reordered_tuple_type,
                           std::tuple<int, long, short, double, float>>{}, "!");

Demo.


P.S。这是我的版本 filter:

template<typename A, typename F>
using filter_one = std::conditional_t<F::template apply<A>::value,
                                      typelist<A>, typelist<>>;

template<typename F, typename... Args>
concat_t<filter_one<Args, F>...> do_filter(typelist<Args...>);

template <typename TL, typename F>
using filter_t = decltype(do_filter<F>(TL()));