Search code examples
c++stdtupleaggregate-initialization

aggregate-initializable tuple like data structure


I want to store multiple non-movable types in a single variable.

At the very first, I have tried std::tuple at the very first, but it fails.

#include <tuple>

template<typename T>
struct No {
    No(T){}
    No(const No &) = delete;
    No(No &&) = delete;
};

struct Handmade {
    No<int> a;
    No<double> b;
    No<char> c;
};

template<typename T>
auto no() -> No<T> { return No<T>(T()); }

auto main() -> int
{
    Handmade h = {no<int>(), no<double>(), no<char>()}; // good
    auto tuple = std::make_tuple(no<int>(), no<double>(), no<char>()); // fails
    return 0;
}

Here, Handmade type can be initialized through aggregate initialization. However, std::tuple is not aggregate-initialzable, it doesn't work.

Since it should be variadic, I cannot write such type Handmade for my purpose.

Is it possible to implement such variadic tepmlate data structure in current C++ standard or is there any workaround?


Solution

  • Yes, you can write your own aggregate tuple like this:

    template <int I, typename T>
    struct MyTupleElem
    {
        T value{};
        template <int J> requires(I == J)       T &get()       {return value;}
        template <int J> requires(I == J) const T &get() const {return value;}
    };
    
    template <typename T, typename ...P>
    struct MyTupleHelper;
    template <int ...I, typename ...P>
    struct MyTupleHelper<std::integer_sequence<int, I...>, P...>
        : MyTupleElem<I, P>...
    {
        using MyTupleElem<I, P>::get...;
    };
    
    template <typename ...P>
    struct MyTuple : MyTupleHelper<std::make_integer_sequence<int, sizeof...(P)>, P...> {};
    
    template <typename ...P>
    MyTuple(P &&...) -> MyTuple<std::decay_t<P>...>;
    
    template <typename T>
    struct No
    {
        T value;
        No(T value) : value(value) {}
        No(const No &) = delete;
        No(No &&) = delete;
    };
    
    template <typename T>
    No<T> no(T t) {return No<T>(t);}
    
    int main()
    {
        MyTuple h = {no<int>(1), no<double>(2.3), no<char>('4')};
        std::cout << h.get<0>().value << '\n';
        std::cout << h.get<1>().value << '\n';
        std::cout << h.get<2>().value << '\n';
    }
    

    And I think it's a good way to make tuples in general, even if you don't want aggregate-ness. Last time I tested, such tuples could tolerate more elements than the classic ones with the bases chained on top of each other.