I found the following code (by phoeen) instanciating a variant based on index at runtime:
template <typename... Ts>
std::variant<Ts...> make_variant(std::size_t i)
{
assert(i < sizeof...(Ts));
static constexpr auto table = std::array{ +[](){ return std::variant<Ts...>{Ts{ }}; }... };
return table[i]();
}
It works (for me, c++17, gcc 8.5), but I don't understand the instantiation of the array table
:
+
before the lambda ?
- Why
std::array
has no template types here ?
C++17 added class template argument deduction (CTAD) which makes it possible to deduce the class template template parameters from the supplied arguments, either via implicit guides or by user provided deduction guides. For std::array
there is one user provided deduction guide:
template< class T, class... U >
array( T, U... ) -> array<T, 1 + sizeof...(U)>;
That is, if you create an array with std::array{1, 2, 3}
, the T
will deduced to int
and 1 + sizeof...(U)
(1 + 2) will be the non-type size argument of the std::array
, so it becomes std::array<int, 3>
.
- What is the goal of the
+
before the lambda ?
Each lambda becomes a unique type, but the unary plus can be used on a non-capturing lambda to get a regular function pointer to it. In this case, all the lambdas will have the type std::variant<Ts...>(*)()
, that is, a pointer to a function taking no arguments and returning std::variant<Ts...>
.
- How this fold expression works ?
It's not a fold expression but parameter expansion.
For each of the Ts...
, create a lamba that returns a std::variant<Ts...>
initialized with the specific Ts
type. Example:
auto v = make_variant<int, double>(1);
Will instantiate this function:
std::variant<int, double> make_variant(std::size_t i) {
static constexpr std::array<std::variant<int, double>(*)(), 2> table{
+[]() { return std::variant<int, double>{int{}}; },
+[]() { return std::variant<int, double>{double{}}; },
};
return table[i](); // return the variant initialized at pos `i`
}