c++macrosvariadic-macrosc++-templates

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


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?


Solution

  • 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<...>.