三角化元组

Triangularizing a tuple

本文关键字:元组 三角      更新时间:2023-10-16

我一直在尝试使用 C++ 的现代功能从 2012 年开始升级(并稍微调整(这个解决方案。

我的目标实际上比这个问题稍微简单一些;我想要这个:

triangularize_t<T0, T1, ..., TN-1, TN>

等效于此:

std::tuple<
std::tuple<>,
std::tuple<T0>,
std::tuple<T0, T1>,
...
std::tuple<T0, T1, ..., TN-1>
>

这样std::tuple_element_t<N, result>就有N元素。类型TN不应出现在输出中的任何位置。

以下是我到目前为止所做的工作:

template <class _Tuple, class _Seq>
struct _tuple_head;
template <class _Tuple, size_t... _N>
struct _tuple_head<_Tuple, std::index_sequence<_N...>> {
using type = std::tuple<std::tuple_element_t<_N, _Tuple>...>;
};

template <class _Tuple, class _Seq>
struct _triangularize_impl;
template <class _Tuple, size_t... _N>
struct _triangularize_impl<_Tuple, std::index_sequence<_N...>> {
using type = std::tuple<typename _tuple_head<_Tuple, std::make_index_sequence<_N>>::type...>;
};

template <class... _Pack>
struct triangularize {
using type = _triangularize_impl<std::tuple<_Pack...>, std::index_sequence_for<_Pack...>>;
};

template <class... _Pack>
using triangularize_t = typename triangularize<_Pack...>::type;

但是,它似乎并没有像我期望的那样扩展。 使用triangularize_t<int, float>作为测试,错误消息似乎报告std::get<0>1的输出返回这些类型(由于某种原因相同(。

_triangularize_impl<tuple<std::__1::tuple<int, float> >, __make_integer_seq<integer_sequence, unsigned long, 1UL> >
_triangularize_impl<tuple<std::__1::tuple<int, float> >, __make_integer_seq<integer_sequence, unsigned long, 1UL> >

我在这里错过了什么?

也许有人可以用更简单的方式做到这一点......但是下面呢?

template <typename T, std::size_t ... Is>
auto gtt_helper (std::index_sequence<Is...>)
-> std::tuple<std::tuple_element_t<Is, T>...>;
template <typename ... Ts, std::size_t ... Is>
auto getTriTuple (std::index_sequence<Is...>)
-> std::tuple<decltype(gtt_helper<std::tuple<Ts...>>
(std::make_index_sequence<Is>{}))...>;
template <typename ... Ts>
using triTuple
= decltype(getTriTuple<Ts...>(std::index_sequence_for<Ts...>{}));

以下是完整的编译C++14示例

#include <type_traits>
#include <utility>
#include <tuple>
template <typename T, std::size_t ... Is>
auto gtt_helper (std::index_sequence<Is...>)
-> std::tuple<std::tuple_element_t<Is, T>...>;
template <typename ... Ts, std::size_t ... Is>
auto getTriTuple (std::index_sequence<Is...>)
-> std::tuple<decltype(gtt_helper<std::tuple<Ts...>>
(std::make_index_sequence<Is>{}))...>;
template <typename ... Ts>
using triTuple
= decltype(getTriTuple<Ts...>(std::index_sequence_for<Ts...>{}));
int main () 
{
using T0 = triTuple<char, int, long, long long>;
using T1 = std::tuple<std::tuple<>,
std::tuple<char>,
std::tuple<char, int>,
std::tuple<char, int, long>>;
static_assert( std::is_same<T0, T1>::value, "!" );
}

为了回答您的问题("我在这里错过了什么?"(,您错过了typename::typetriangularize

在我看来,正确的版本应该是

template <class... _Pack>
struct triangularize {
// ..........VVVVVVVV  add typename
using type = typename _triangularize_impl<std::tuple<_Pack...>,
std::index_sequence_for<_Pack...>>::type ;
// and add ::type ..........................................................^^^^^^
};

不幸的是,您的(更正的(代码似乎适用于 clang++,但不适用于 g++;我怀疑有一个 g++ 错误,但我不确定。

使用 Boost.Mp11 这是...不幸的是,不是单行。它需要几行。

我们定义一个函数来执行单个操作:给定所有内容的列表和下一个元素,附加该元素(也就是说,这将我们从第N个解决方案带到第N+1个解决方案(:

template <typename L, typename T>
using add_one = mp_push_back<L, mp_push_back<mp_back<L>, T>>;

现在折叠它 - 它只是依次为每个参数应用该二进制函数:

template <typename... Ts>
using triangularize_t = mp_fold<mp_list<Ts...>, tuple<tuple<>>, add_one>;

并检查它是否正确:

static_assert(std::is_same_v<triangularize_t<>,
tuple<tuple<>>>);
static_assert(std::is_same_v<triangularize_t<int>,
tuple<tuple<>, tuple<int>>>);
static_assert(std::is_same_v<triangularize_t<int, char>,
tuple<tuple<>, tuple<int>, tuple<int, char>>>);

我们可以将其推广到适用于任何类模板,而不仅仅是元组,方法是triangularize更改为使用输入列表并从输入参数推断其初始值:

template <typename L>
using triangularize_t = mp_fold<L, mp_push_back<mp_clear<L>, mp_clear<L>>, add_one>;

这也允许:

static_assert(std::is_same_v<triangularize_t<mp_list<int, char>>,
mp_list<mp_list<>, mp_list<int>, mp_list<int, char>>>);

或者您可能想要使用的任何其他列表(特别是不是variant,因为variant<>格式不正确(。

使用 Boost.Mp11 这是一个单行代码。我只是上次不够努力。此外,此解决方案符合OP的确切规格:

template <typename... Ts>
using triangularize_t =
mp_transform_q<
mp_bind_front<mp_take, std::tuple<Ts...>>,
mp_rename<mp_iota_c<sizeof...(Ts)>, std::tuple>
>;

莱姆解释这是做什么的,假设Ts...<int, char>

  • mp_iota_c<sizeof...(Ts)>给出了序列mp_list<mp_int<0>, mp_int<1>>
  • mp_rename将一种"列表"类型换成另一种"列表"类型,在本例中mp_liststd::tuple,因此您可以获得std::tuple<mp_int<0>, mp_int<1>>
  • mp_bind_front<mp_take, std::tuple<Ts...>>动态创建一个元函数,该元函数将接受一个参数并将其应用于完整tuple<Ts...>上的mp_takemp_take从给定列表中获取前N件事。如果我们mp_int<1>通过这个,在我们最初的tuple<int, char>,我们会得到tuple<int>
  • mp_transform_q对列表中的每个元素调用提供的元函数。我们采用我们的tuple<mp_int<0>, mp_int<1>>并将其扩展到tuple<mp_take<tuple<int, char>, mp_int<0>>, mp_take<tuple<int, char>, mp_int<1>>>,这是tuple<tuple<>, tuple<int>>。如愿以偿。

要将其更改为我的另一个答案(将<int>三角化为tuple<tuple<>, tuple<int>>(,我们可以将sizeof...(Ts)更改为sizeof...(Ts)+1

为了扩展它以支持任何列表类型(不仅仅是tuple(,我们可以更改这里的元函数来获取列表而不是包,并使用提供的列表类型作为解决方案。在某些方面,这使得解决方案更容易:

template <typename L>
using triangularize_t =
mp_transform_q<
mp_bind_front<mp_take, L>,
mp_append<mp_clear<L>, mp_iota<mp_size<L>>>
>;
template <typename... Ts>
using triangularize_t = triangularize_list<std::tuple<Ts...>>;

这里尴尬的部分是mp_append<mp_clear<L>, mp_iota<mp_size<L>>>.基本上,我们需要序列列表与原始列表具有相同的列表类型。以前,我们可以使用mp_rename因为我们知道我们需要一个元组。但是现在,我们没有将列表作为类模板 - 只有它的实例。可能有比mp_append<mp_clear<L>, U>更好的方法可以做到这一点......但这就是我目前所拥有的。

我们可以通过使用多个参数包的部分专用化来避免使用任何index_sequence

#include <tuple>
template <typename Tuple, typename LastTuple, typename First, typename... Rest>
struct triangulate;
template <typename Tuple, typename LastTuple, typename Last>
struct triangulate<Tuple, LastTuple, Last> {
using type = Tuple;
};
template <typename First, typename Second, typename... TupleTypes, typename... LastTupleTypes, typename... Rest>
struct triangulate<std::tuple<TupleTypes...>, std::tuple<LastTupleTypes...>, First, Second, Rest...> {
using next = std::tuple<LastTupleTypes..., First>;
using type = typename triangulate<std::tuple<TupleTypes..., next>, next, Second, Rest...>::type;
};
template <typename... T>
using triangularize_t = typename triangulate<std::tuple<std::tuple<>>, std::tuple<>, T...>::type;

将最终产品作为第一个参数(std::tuple<std::tuples...>>(传递,第二个参数是我们使用的最后一个std::tuple<...>

然后,我们递归地将下一个参数添加到最后一个元组,并将该元组添加到最终结果的末尾。