Равномерная инициализация по кортежу - PullRequest
25 голосов
/ 05 июля 2019

Сегодня я пришел к ситуации, когда у меня есть вектор кортежей, где кортежи могут содержать несколько записей.Теперь я хотел преобразовать мой вектор кортежей в вектор объектов так, чтобы записи кортежей точно соответствовали равномерной инициализации моего объекта.

Следующий код делает работу за меня, но это немного неуклюже.Я спрашиваю себя, возможно ли получить общее решение, которое может создать объекты, если кортежи точно соответствуют равномерному порядку инициализации объектов.

Это может быть очень желательной функциональностью, когда количество передаваемых параметров увеличивается.

#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;
}

Ответы [ 3 ]

18 голосов
/ 05 июля 2019

Предоставить Object и std::tuple конструктор. Вы можете использовать std::tie, чтобы назначить своих членов:

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

Теперь он автоматически создается:

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

Чтобы уменьшить объем копирования, вы можете заменить auto v на const auto& v и заставить конструктор принять const std::tuple<Args...>& t.


Кроме того, рекомендуется обращаться к контейнеру-источнику через const итератор:

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

13 голосов
/ 05 июля 2019

Вот неинтрузивная версия (то есть не касающаяся Object), которая извлекает количество указанных членов данных. Обратите внимание, что это зависит от агрегатной инициализации.

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>{});
}

Вы вызываете это так:

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

Или без лямбды обтекания:

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

Как указывал @Deduplicator, вышеупомянутые вспомогательные шаблоны реализуют части std::apply, которые можно использовать вместо этого.

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); });
12 голосов
/ 05 июля 2019

Начиная с C ++ 17, вы можете использовать std :: make_from_tuple :

std::transform(values.begin(),
               values.end(),
               std::back_inserter(objs),
               [](const auto& t)
        {
            return std::make_from_tuple<Object>(t);
        });

Примечание: требуется соответствующий конструктор для Object.

...