如何逐步创建类型数组?

How can I incrementally create an array of types?

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;
};

例如,在 C++ 中使用 std::tuple 创建“类型数组”非常简单,即创建具有某些类型的元组,可以使用编译时循环迭代,等等定义一个应用于每个类型的回调,它是 std::tuple 的模板参数的一部分。所以假设有一个类型叫做

using Arr = TypesArray<int>;

我需要 Arr 在某个时刻可以“更新”到,例如 TypesArray<int, double>。一切都在编译时已知。但是在 C++ 中我不能做这样的事情

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

但例如我可以这样做

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

如何将内部 Arr 的可见性限制在内部块中,因此在那种情况下,我应该在最内部的块中使用模板函数。但是,我应该为这个解决方案找到一种方法,让编译器生成这些递归嵌套块的任意深度,然后(可能)传递给最后一个函数作为终止步骤。 我不能做的是逐步定义一个类型数组,理想情况下做这样的事情:

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

我想到了一个宏计数器,但是所有的东西都会被包含在一个宏中所以我不能有一个计数器例如使用 boost 预处理器库,并且 gcc 的默认 __COUNTER__ 使用起来不安全像一个变量(它可以在其他语句中改变)。

这是一个有状态元编程的工作。请参阅 unconstexpr 以获取灵感。

用法示例:run on gcc.godbolt.org(实现在答案底部)

#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>
}

这里最大的怪癖是每个列表操作都需要提供一个唯一的类型,否则编译器倾向于缓存类型并避免更新它们。

正如您将在下面看到的,我的 PushBack 默认为此使用插入的类型(它会自动拒绝重复类型;如果这不是您想要的,请传递自定义唯一类型),并且对于Elems 我手动传递 decltype([]{}),每次都会产生一个唯一的类型。

您可以将 decltype([]{}) 放入默认模板参数中以避免手动传递唯一​​类型,但上次我尝试时,它并不能在所有编译器上可靠地工作,也许现在情况好多了.

实施:

#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;
}