由元组统一初始化

时间:2019-07-05 08:09:00

标签: c++ stl stl-algorithm stdtuple uniform-initialization

今天,我遇到了这样一种情况:我有一个元组向量,其中元组可能包含多个条目。现在,我想将元组的向量转换为对象的向量,以使元组的条目与我的对象的统一初始化完全匹配。

以下代码为我完成了工作,但是有点笨拙。我在问自己,如果元组完全符合对象的统一初始化顺序,是否有可能派生出通用的解决方案来构造对象。

当要传递的参数数量增加时,这可能是非常理想的功能。

#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 个答案:

答案 0 :(得分:18)

提供Objectstd::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), ...

答案 1 :(得分:13)

这是一种非侵入式版本(即不触碰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); });

或者,没有包装的lambda:

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 :(得分:12)

从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的适当构造函数。