c++ - Inicialización uniforme por tupla.
stl stl-algorithm (3)
Hoy, llegué a una situación, donde tengo un vector de tuplas, donde las tuplas pueden contener varias entradas. Ahora quería convertir mi vector de tuplas en un vector de objetos, de modo que las entradas de las tuplas coincidan exactamente con la inicialización uniforme de mi objeto.
El siguiente código hace el trabajo por mí, pero es un poco torpe. Me pregunto si podría ser posible obtener una solución genérica que pueda construir los Objetos si las tuplas coinciden exactamente con el orden de inicialización uniforme de los objetos.
Esta podría ser una funcionalidad muy deseable, cuando el número de parámetros para pasar crezca.
#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;
}
Aquí hay una versión no intrusiva (es decir, no tocar
Object
) que extrae el número de miembros de datos especificados.
Tenga en cuenta que esto se basa en la inicialización agregada.
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>{});
}
Lo invocas así:
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
[](const auto& v)->Object { return createAggregate<Object, 3>(v); });
O, sin la envoltura lambda:
std::transform(values.cbegin(), values.cend(), std::back_inserter(objs),
createAggregate<Object, 3, decltype(values)::value_type>);
Como @Deduplicator señaló, las plantillas de ayuda anteriores implementan partes de
std::apply
, que pueden usarse en su lugar.
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); });
Desde C ++ 17, puedes usar std::make_from_tuple :
std::transform(values.begin(),
values.end(),
std::back_inserter(objs),
[](const auto& t) -> Object
{
return std::make_from_tuple(t);
});
Proporcionar
Object
un constructor
std::tuple
.
Puedes usar
std::tie
para asignar a tus miembros:
template<typename ...Args>
Object(std::tuple<Args...> t) {
std::tie(s, i, d) = t;
}
Ahora se construye automáticamente:
std::transform(values.begin(), values.end(), std::back_inserter(objs),
[](auto v) -> Object {
return { v };
});
Para reducir la cantidad de copias, es posible que desee reemplazar
auto v
con
const auto& v
y hacer que el constructor acepte una
const std::tuple<Args...>& t
.
Además, es una buena práctica acceder al contenedor de origen a través de
const
iterator:
std::transform(
values.cbegin(), values.cend()
, std::back_inserter(objs), ...