c++stlstl-algorithmstdtupleuniform-initialization

Uniform initialization by tuple


Today, I arrived at a situation, where I have a vector of tuples, where the tuples might contain several entries. Now I wanted to convert my vector of tuples to a vector of objects, such that the entries of the tuples will exactly match the uniform initialization of my object.

The following code does the job for me, but it is a bit clumsy. I'm asking myself if it might be possible to derive a generic solution that can construct the Objects if the tuples matches exactly the uniform initialization order of the objects.

This might be a very desirable functionality, when the number of parameters to pass grows.

#include <vector>
#include <tuple>
#include <string>
#include <algorithm>

struct Object
{
    std::string s;
    int i;
    double d;
};

int main() {
    std::vector<std::tuple<std::string, int, double>> values = { {"A",0,0.},{"B",1,1.} };

    std::vector<Object> objs;
    std::transform(values.begin(), values.end(), std::back_inserter(objs), [](auto v)->Object
        {
        // This might get tedious to type, if the tuple grows
            return { std::get<0>(v), std::get<1>(v), std::get<2>(v) };
           // This is my desired behavior, but I don't know what magic_wrapper might be
            // return magic_wrapper(v);
        });

    return EXIT_SUCCESS;
}

Solution

  • Here is a non-intrusive version (i.e. not touching Object) that extracts the number of specified data members. Note that this relies on aggregate initialization.

    template <class T, class Src, std::size_t... Is>
    constexpr auto createAggregateImpl(const Src& src, std::index_sequence<Is...>) {
       return T{std::get<Is>(src)...};
    }
    
    template <class T, std::size_t n, class Src>
    constexpr auto createAggregate(const Src& src) {
       return createAggregateImpl<T>(src, std::make_index_sequence<n>{});
    }
    

    You invoke it like this:

    std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
         [](const auto& v)->Object { return createAggregate<Object, 3>(v); });
    

    Or, without the wrapping lambda:

    std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
       createAggregate<Object, 3, decltype(values)::value_type>);
    

    As @Deduplicator pointed out, the above helper templates implement parts of std::apply, which can be used instead.

    template <class T>
    auto aggregateInit()
    {
       return [](auto&&... args) { return Object{std::forward<decltype(args)>(args)...}; };
    }
    
    std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
        [](const auto& v)->Object { return std::apply(aggregateInit<Object>(), v); });