c++c++17variantfold-expression

I don't understand this initialisation of an std::array of lambda with pack expansion


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:

  1. Why std::array has no template types here ?
  2. What is the goal of the + before the lambda ?
  3. How this fold expression works ?

Solution

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

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

    1. 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`
    }