Home > Software engineering >  How can I incrementally create an array of types?
How can I incrementally create an array of types?

Time:05-03

template<typename ...Args>
class TypesArray {
    std::tuple<Args...> *tuple_;

    template<typename T>
    struct push_back_s {
        using type = TypesArray<Args..., T>;
    };


    TypesArray(const Args &... t) {

    }

public:

    template<typename T>
    using push_back = typename push_back_s<T>::type;
};

Using, for example, std::tuple in c , is quite simple to create an "array of types", that is create a tuple with some types, which one can iterate using compile time loop, and so define a callback which is applied to each type which is part of template args of std::tuple. So supposing to have a type called

using Arr = TypesArray<int>;

I need that Arr, at a certain point, can be "updated" to, for example, to TypesArray<int, double>. Everything is known at compile time. However in c I can't doing something like this

using Arr = TypesArray<int>;
using Arr = Arr::push_back<double>;

but for example I could do this

using Arr = TypesArray<int>;
{
    using Arr = Arr::push_back<double>;
    {
        using Arr = Arr::push_back<float>;
        MyFunc<Arr>();
    }
}

How ever the visibilty of the inner Arr is limited at the inner block, so in that case I should use a template function inside the most inner block. However I should find for this solution a way to make generate to compiler an arbitrary depth of these recursive nested blocks and then (probably) pass to the last a function as termination step. What I can't do is to define incrementally an array of types, ideally doing a thing like this:

using Arr = TypesArray<int>;
....
using Arr = Arr::push_back<double>;
...
using Arr = Arr::push_back<float>;
...
MyFunc<Arr>(); // MyFunc<TypesArray<int, double, float>>

I thought at a macro counter, but everything will be enclosed in a macro so I can't have a counter for exampled using boost preprocessor library, and the default __COUNTER__ of gcc is not safe to use like a variable (it could be altered in other statements).

CodePudding user response:

This is a job for stateful metaprogramming. See unconstexpr for inspiration.

Example usage: run on gcc.godbolt.org (implementation is at the bottom of the answer)

#include <iostream>

template <typename T>
void PrintType()
{
    #ifdef _MSC_VER
    std::cout << __FUNCSIG__ << '\n';
    #else
    std::cout << __PRETTY_FUNCTION__ << '\n';
    #endif
}

int main()
{
    struct TypesArray {};
    PrintType<List::Elems<std::tuple, TypesArray, decltype([]{})>>(); // std::tuple<>
    (void)List::PushBack<TypesArray, int>{};
    PrintType<List::Elems<std::tuple, TypesArray, decltype([]{})>>(); // std::tuple<int>
    (void)List::PushBack<TypesArray, double>{};
    PrintType<List::Elems<std::tuple, TypesArray, decltype([]{})>>(); // std::tuple<int, double>
    (void)List::PushBack<TypesArray, float>{};
    PrintType<List::Elems<std::tuple, TypesArray, decltype([]{})>>(); // std::tuple<int, double, float>
}

The big quirk here is that every list operation needs to be fed a unique type, because otherwise compilers tend to cache types and avoid updating them.

As you'll see below, my PushBack by default uses the inserted type for this (which automatically rejects duplicate types; if this is not what you want, pass a custom unique type), and for Elems I manually pass decltype([]{}), which yields a unique type every time.

You might be able to put decltype([]{}) into a default template argument to avoid passing unique types manually, but last time I tried it, it didn't work reliably on all compilers, maybe things are better now.

Implementation:

#include <cstddef>
#include <utility>

template <typename T> struct tag {using type = T;};

namespace List
{
    namespace impl
    {
        template <typename Name, std::size_t Index>
        struct ElemReader
        {
            #if defined(__GNUC__) && !defined(__clang__)
            #pragma GCC diagnostic push
            #pragma GCC diagnostic ignored "-Wnon-template-friend"
            #endif
            friend constexpr auto adl_ImpListElem(ElemReader<Name, Index>);
            #if defined(__GNUC__) && !defined(__clang__)
            #pragma GCC diagnostic pop
            #endif
        };

        template <typename Name, std::size_t Index, typename Value>
        struct ElemWriter
        {
            friend constexpr auto adl_ImpListElem(ElemReader<Name, Index>)
            {
                return tag<Value>{};
            }
        };

        constexpr void adl_ImpListElem() {} // A dummy ADL target.

        template <typename Name, std::size_t Index, typename Unique, typename = void>
        struct CalcSize : std::integral_constant<std::size_t, Index> {};

        template <typename Name, std::size_t Index, typename Unique>
        struct CalcSize<Name, Index, Unique, decltype(void(adl_ImpListElem(ElemReader<Name, Index>{})))> : CalcSize<Name, Index   1, Unique> {};

        template <typename Name, std::size_t Index, typename Unique>
        using ReadElem = typename decltype(adl_ImpListElem(ElemReader<Name, Index>{}))::type;

        template <template <typename...> typename List, typename Name, typename I, typename Unique>
        struct ReadElemList {};
        template <template <typename...> typename List, typename Name, std::size_t ...I, typename Unique>
        struct ReadElemList<List, Name, std::index_sequence<I...>, Unique> {using type = List<ReadElem<Name, I, Unique>...>;};
    }

    struct DefaultUnique {};

    // Calculates the current list size.
    template <typename Name, typename Unique = DefaultUnique>
    inline constexpr std::size_t size = impl::CalcSize<Name, 0, Unique>::value;

    // Touch this type to append `Value` to the list.
    template <typename Name, typename Value, typename Unique = Value>
    using PushBack = impl::ElemWriter<Name, size<Name, Unique>, Value>;

    // Returns the type previously passed to `WriteState`, or causes a SFINAE error.
    template <typename Name, std::size_t I, typename Unique = DefaultUnique>
    using Elem = impl::ReadElem<Name, I, Unique>;

    template <template <typename...> typename List, typename Name, typename Unique = DefaultUnique>
    using Elems = typename impl::ReadElemList<List, Name, std::make_index_sequence<size<Name, Unique>>, Unique>::type;
}
  • Related