Triangularizing a tuple
Maybe someone can make it in a simpler way... but what about as follows?
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...>{}));
The following is a full compiling C++14 example
#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, "!" );
}
To respond to your question ("What have I missed here?"), you have missed a typename
and a ::type
in triangularize
It seems to me that the right version should be
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 ..........................................................^^^^^^
};
Unfortunately, your (corrected) code seems to works with clang++ but not with g++; I suspect a g++ bug but I'm not sure.
With Boost.Mp11 this is... unfortunately not a one-liner. It takes a couple lines instead.
We define one function to perform a single action: given a list of everything and the next element, append that one (that is, this takes us from the N
th solution to the N+1
st solution):
template <typename L, typename T>
using add_one = mp_push_back<L, mp_push_back<mp_back<L>, T>>;
And now fold over that - which just applies that binary function for each argument in turn:
template <typename... Ts>
using triangularize_t = mp_fold<mp_list<Ts...>, tuple<tuple<>>, add_one>;
And check that it's correct:
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>>>);
We can generalize this to work on any class template instead of solely tuple by changing triangularize
to use an input list and deduce its initial value from the input argument:
template <typename L>
using triangularize_t = mp_fold<L, mp_push_back<mp_clear<L>, mp_clear<L>>, add_one>;
Which also allows:
static_assert(std::is_same_v<triangularize_t<mp_list<int, char>>,
mp_list<mp_list<>, mp_list<int>, mp_list<int, char>>>);
Or whatever other lists you might want to use (notably not variant
, since variant<>
is ill-formed).
With Boost.Mp11 this is a one-liner. I just didn't try hard enough last time. Also this solution matches OP's exact specification:
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>
>;
Lemme explain what this does, assuming Ts...
is <int, char>
.
mp_iota_c<sizeof...(Ts)>
gives the sequencemp_list<mp_int<0>, mp_int<1>>
.mp_rename
swaps out one "list" type for another, in this casemp_list
forstd::tuple
so you getstd::tuple<mp_int<0>, mp_int<1>>
.mp_bind_front<mp_take, std::tuple<Ts...>>
creates a metafunction on the fly that will take an argument and apply it tomp_take
on the fulltuple<Ts...>
.mp_take
takes the firstN
things from the given list. If we passed inmp_int<1>
to this, on our initialtuple<int, char>
, we'd gettuple<int>
.mp_transform_q
calls the provided metafunction on each element in the list. We take ourtuple<mp_int<0>, mp_int<1>>
and expand it out intotuple<mp_take<tuple<int, char>, mp_int<0>>, mp_take<tuple<int, char>, mp_int<1>>>
which istuple<tuple<>, tuple<int>>
. As desired.
To change this into my other answer (which triangularizes <int>
into tuple<tuple<>, tuple<int>>
), we can change sizeof...(Ts)
into sizeof...(Ts)+1
.
To extend this to support any list type (not just tuple
), we can change the metafunction here to take a list instead of a pack and use the provided list type as a solution. In some respects, this makes the solution easier:
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...>>;
The awkward part here is the mp_append<mp_clear<L>, mp_iota<mp_size<L>>>
. Basically, we need the sequence list to have the same list type as the original list. Before, we could use mp_rename
because we know we needed a tuple. But now, we don't have the list as a class template - just have an instance of it. There might be a better way to do this than mp_append<mp_clear<L>, U>
... but this is what I have so far.