Compile-time generation of array using an interleaved formatter

Viewed 53

I am trying to build a compile-time generated array of a certain type, based on a template parameter pack of some types. The array generation works just fine if the types are used directly. However, I'd like to apply a tag on each each and have them be interleaved in the generated array.

So, this is where I am at:

template <typename Head, typename... Tail>
static constexpr attributes_t make_attrs = {...};

and instantiated by (size is calculated elsewhere)

attributes_t attributes[sizeof...(Ts)] = {make_attrs<Ts>...};

What I would really like is to be able to make the latter tag each type in Ts, say with First and Second tags, such that I can produce a type-list like this

NewTs = First<Ts[0]>, Second<Ts[0]>, First<Ts[1]>, Second<Ts[1]>, First<Ts[2]>, Second<Ts[2]>, ...

It's important that the First and Second taggers are interleaved like that. The intent is to be able to control the instantiation of each one by its tagged version.

In my naivity I tried something like this

attributes_t attributes[2 * sizeof...(Ts)] = {make_attrs<First<Ts>, Second<Ts>>...};

in the hopes that it would expand the pack in the interleaved pattern. Unfortunately that didn't work - I am hoping that there exists a similarly simple and concise solution :)

There must be a way to do this, I'm sure. I have looked through various posts here and there and cannot find a solution that has worked yet, or perhaps I just didn't quite understand it.

I hope you have a great solution :)

1 Answers

As usual std::index_sequence might help:

template <typename T> struct First{};
template <typename T> struct Second{};

template <typename... Ts>
struct Types
{
};

template <typename Seq, typename Tuple>
struct Impl;

template <std::size_t... Is, typename Tuple>
struct Impl<std::index_sequence<Is...>, Tuple>
{
    using type =
        Types<std::conditional_t<Is % 2 == 0,
                                 First<std::tuple_element_t<Is / 2, Tuple>>,
                                 Second<std::tuple_element_t<Is / 2, Tuple>>>...>;
};


template <typename... Ts>
using makeRes = typename Impl<std::make_index_sequence<2 * sizeof...(Ts)>,
                              std::tuple<Ts...>>::type;

static_assert(std::is_same_v<Types<First<int>, Second<int>,
                                   First<char>, Second<char>>,
                             makeRes<int, char>>);

Demo

if attributes_t is default constructible, assigning might be shorter:

template <typename... Ts>
constexpr std::array<attributes_t, 2 * sizeof...(Ts)> make_attributes()
{
    std::array<attributes_t, 2 * sizeof...(Ts)> res{};
    std::size_t index = 0;
    ((attributes[index++] = make_attrs<First<Ts>>,
      attributes[index++] = make_attrs<Second<Ts>>),
     ...);
    return res;
}

Folding expression is C++17, you can workaround that in C++11/C++14 with initializer_list trick:

template <typename... Ts>
constexpr std::array<attributes_t, 2 * sizeof...(Ts)> make_attributes()
{
    std::array<attributes_t, 2 * sizeof...(Ts)> res{};
    std::size_t index = 0;
    const int dummy[] = {0, (
        attributes[index++] = make_attrs<First<Ts>>,
        attributes[index++] = make_attrs<Second<Ts>>,
        0)...};
    static_cast<void>(dummy); // silent warning for unused variable
    return res;
}
Related