Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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; } 
like image 933
Aleph0 Avatar asked Jul 05 '19 08:07

Aleph0


2 Answers

Provide Object an std::tuple constructor. You can use std::tie to assign your members:

template<typename ...Args> Object(std::tuple<Args...> t) {     std::tie(s, i, d) = t; } 

Now it gets automatically constructed:

std::transform(values.begin(), values.end(), std::back_inserter(objs),      [](auto v) -> Object {         return { v };     }); 

To reduce the amount of copying you might want to replace auto v with const auto& v and make the constructor accept a const std::tuple<Args...>& t.


Also, it's good practise to access the source container via const iterator:

std::transform(values.cbegin(), values.cend(), std::back_inserter(objs), ...

like image 76
Stack Danny Avatar answered Oct 16 '22 00:10

Stack Danny


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); }); 
like image 26
lubgr Avatar answered Oct 16 '22 01:10

lubgr