Is there a less verbose way to initialize a tuple of heterogenous tuples (or similarly templated types)

84 Views Asked by At

I'm wanting to construct a tuple of hetergenous tuples but this seemingly requires an explicit constructor call for each member:

auto foo = std::tuple{
    std::tuple{1, "foo", 3},
    std::tuple{1, 2, 3},
    std::tuple{1.0, 2.0},
    // many more CTAD constructor calls
}

Basically I'm asking if a helper function, etc could make this look like the below example with homogenous inner types, where the inner constructor doesn't need to be called each time.

auto bar = std::vector<std::tuple<int, std::string_view, int>>{
    {1, "foo", 3},
    {4, "bar", 6},
    {1, "baz", 3},
    // ...
};

So that I can hopefully have something like this:

auto baz = make_tuple_of_tuples(
    {1, "foo", 3},
    {1, 2, 3},
    {1.0, 2.0},
    // ...
);

If this is impossible with std::tuples, what about with a custom type where we control the constructors and deduction guides?

2

There are 2 best solutions below

0
On BEST ANSWER

C++ would require some way of dynamically specifying sets of variadic template arguments (something like template <typename <typename ... T> ... U> or whatever the syntax should look like), which is not supported, so out of luck so far.

You can achieve something similar with a bit of pre-processor magic, though:

#define TUPLE_OF_TUPLES(...) EVAL(CONCAT(TUPLE_OF_TUPLES_, COUNT(__VA_ARGS__))(__VA_ARGS__))

#define EVAL(...) __VA_ARGS__
#define COUNT(...) 2 // TODO: appropriate counting macro!
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y 

#define TUPLE_OF_TUPLES_2(X, Y) EVAL(std::tuple(std::tuple X, std::tuple Y));
// TODO: analogously macros for 1, 3, 4, 5, ... arguments

Counting macro arguments is discussed e.g. here (though in given case you only can use the pre-processor solution!).

Demonstration on godbolt, though only tested for GCC, other compilers (especially MSVC) might or not need adjustments...

Be aware, though, that pre-processor magic is more error prone than standard C++, so use with caution.

0
On

{..} has no types, and can only be deduced for std::initializer_list or C-arrays.

Your usage doesn't match, so you cannot use deduction.

Types should be provided:

auto foo =
    std::tuple<
        std::tuple<int, const char*, int>,
        std::tuple<int, int, int>,
        std::tuple<double, double> /*..*/>{
    {1, "foo", 3},
    {1, 2, 3},
    {1.0, 2.0},
    // many more CTAD constructor calls
    };

or you have to use std::tuple "prefix" for each element (as you do):

auto foo = std::tuple{
    std::tuple{1, "foo", 3},
    std::tuple{1, 2, 3},
    std::tuple{1.0, 2.0},
    // many more CTAD constructor calls
};