How can you define std::variant at compile time if the dependent types are also defined at compile time?

60 views Asked by At

Is it possible to use a macro or something similar to create instances of a template class and generate the code that then adds the created variables to a std::vector that uses std::variant?

Consider this example:

#include <memory>
#include <variant>
#include <vector>

template <typename... Ts>
class Foo
{
};

int main()
{

    // These definitions of Foo shall be produced by the macro
    auto foo1 = std::make_shared<Foo<int, int, int>>(Foo<int, int, int>());
    auto foo2 = std::make_shared<Foo<int, int>>(Foo<int, int>());

    // The variants shall be done with respect to the instantiations of Foo
    std::vector<std::variant<decltype(foo1), decltype(foo2)>> objects;

    return 0;
}

foo1 and foo2 two create two types of the class Foo since they are using different template parameters. Therefore, the std::vector<std::variant<...>> objects is able to hold two variants. If one would define a foo3 with a different type than the two before, it should come with three variants accordingly. Is this possible?

Or a macros in general not capable of handling data types as input?

2

There are 2 answers

0
Artyer On BEST ANSWER

In general, variables of the form foo1, foo2, ..., fooN should be in a container instead. An array won't work because the types are different, but a tuple will.

A helper function can help you call make_shared for each type:

template<typename... InputTypes, typename Factory>
constexpr auto map_types(Factory&& f) {
    return std::make_tuple(f(std::type_identity<InputTypes>{})...);
}

auto foos = map_types<
    Foo<int, int, int>,
    Foo<int, int>
>([]<class T>(std::type_identity<T>) {
    return std::make_shared<T>();
});

// foo1 -> std::get<0>(foos)
// foo2 -> std::get<1>(foos)

// Or for convenience
auto& [ foo1, foo2 ] = foos;

And the type of foos will be std::tuple<std::shared_ptr<Foo<int, int, int>>, std::shared_ptr<Foo<int, int>>>, which you can programmatically transform into your variant type:

using variant_type = decltype([]<typename... T>(std::tuple<T...> const&) -> std::variant<T...> { std::unreachable(); }(foos));
std::vector<variant_type> objects; 

If you wanted to add or remove any types, you only have to change the template parameters given to map_types<...>.

1
user12002570 On

You don't need to use macro with modern c++ as you can write a constexpr variadic function template as shown below:

template<typename... T>
constexpr auto make_foo()
{
    return std::make_shared<Foo<T...>>();
}

auto foo1 = make_foo<int, int, int>();
auto foo2 = make_foo<int, int>();


Working demo