Равномерная инициализация по кортежу
Сегодня я пришел к ситуации, когда у меня есть вектор кортежей, где кортежи могут содержать несколько записей. Теперь я хотел преобразовать мой вектор кортежей в вектор объектов так, чтобы записи кортежей точно соответствовали равномерной инициализации моего объекта.
Следующий код делает работу за меня, но это немного неуклюже. Я спрашиваю себя, возможно ли получить общее решение, которое может создать объекты, если кортежи точно соответствуют равномерному порядку инициализации объектов.
Это может быть очень желательной функциональностью, когда число передаваемых параметров растет.
#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;
}
Ответы
Ответ 1
Вот ненавязчивая версия (то есть не касающаяся 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); });
Ответ 2
Предоставьте 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),...
Ответ 3
Начиная с С++ 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);
});