Basically this problem reduces to just sorting a list of types based on a given comparator. Once you have that, everything else follows. So this answer is just the sorting part. We'll start with a typelist:
template <typename...>
struct typelist {
using type = typelist;
};
I'm going to assume a bunch of metafunctions which are very short (head
, tail
, concat
, size
). I will omit them for brevity.
So let's just jump into writing merge sort:
template <typename TL, typename Cmp = LessSize>
struct sort
{
using left_right = typename split<TL, size<TL>::value/2>::type;
using left = typename sort<head_t<left_right>, Cmp>::type;
using right = typename sort<head_t<tail_t<left_right>>, Cmp>::type;
using type = typename merge<left, right, Cmp>::type;
};
// base case for exactly 1 element
template <typename T, typename Cmp>
struct sort<typelist<T>, Cmp> {
using type = typelist<T>;
};
// potentially add a base case for exactly 2 elements here?
The general structure here should look familiar. We split our typelist, TL
, into two equal parts, sort both, and then merge. Of course, this is metaprogramming, so everything is unnecessarily complicated.
Let's start with split
. split
takes a typelist and a size, and returns a typelist of two typelists: the first having the given size, and the second being the remainder:
template <typename A, typename B, size_t N>
struct split_impl
: std::conditional<
size<A>::value < N,
split_impl<concat_t<A, typelist<head_t<B>>>, tail_t<B>, N>,
typelist<A, B>
>::type
{ };
template <typename TL, size_t N>
struct split
: split_impl<typelist<>, TL, N>
{ };
So that gives us left
and right
(at least once we apply head_t<>
and head_t<tail_t<>>
). All that's left is the merge
step. I'm using the Boost MPL idea of what a metafunction class is, so LessSize
is:
struct LessSize {
template <typename A, typename B>
using apply = std::integral_constant<bool, sizeof(A) < sizeof(B)>;
};
merge
just has to walk both typelists and pick the smallest element based on the comparator between the two typelists. First, we'll start with all of our base cases:
template <typename L, typename R, typename Cmp>
struct merge;
// R empty
template <typename... T, typename Cmp>
struct merge<typelist<T...>, typelist<>, Cmp> {
using type = typelist<T...>;
};
// L empty
template <typename... T, typename Cmp>
struct merge<typelist<>, typelist<T...>, Cmp> {
using type = typelist<T...>;
};
And then the recursive step, which is somewhat ugly:
template <typename A, typename... As, typename B, typename... Bs, typename Cmp>
struct merge<typelist<A, As...>, typelist<B, Bs...>, Cmp>
: std::conditional<
Cmp::template apply<A, B>::value,
concat_t<typelist<A>, typename merge<typelist<As...>, typelist<B, Bs...>, Cmp>::type>,
concat_t<typelist<B>, typename merge<typelist<A, As...>, typelist<Bs...>, Cmp>::type>
>::type
{ };
Basically, given two typelists, {A, As...}
and {B, Bs...}
, we select the smallest based on Cmp
, and that's the side we're popping the element off of. If Cmp::apply<A,B>
, then we're concatenating A
with the result of merging {As...}
with {B, Bs...}
. And vice versa.
And that's all she wrote:
template <typename T>
struct TD;
int main()
{
using T = sort<typelist<int, double, char, float>, LessSize>::type;
TD<T> r;
}
main.cpp: In function 'int main()':
main.cpp:131:11: error: aggregate 'TD<typelist<char, float, int, double> > r' has incomplete type and cannot be defined
TD<T> r;
^
Once you have the sorted types, making a tuple is straightforward:
template <template <typename...> class C>
struct meta_quote {
template <typename... T>
using apply = C<T...>;
};
template <typename F, typename TL>
struct meta_apply;
template <typename F, typename... T>
struct meta_apply<F, typelist<T...>> {
using type = typename F::template apply<T...>;
};
template <typename... T>
struct my_tuple
: meta_apply<meta_quote<std::tuple>,
typename sort<typelist<T...>>::type
>::type;
{
using base_tuple = meta_apply<...>;
};
Now just add overloads for get<>
on my_tuple<T...>
:
template <size_t I, typename... T>
auto get(my_tuple<T...>& t) {
using type = std::tuple_element_t<I, std::tuple<T...>>;
return std::get<type>(static_cast<typename my_tuple<T...>::base_type&>(t));
}