【问题标题】:Uniform initialization by tuple元组统一初始化
【发布时间】:2019-11-15 20:26:23
【问题描述】:

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

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

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

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

【问题讨论】:

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


    【解决方案1】:

    提供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&amp; v,并使构造函数接受const std::tuple&lt;Args...&gt;&amp; t


    另外,最好通过const迭代器访问源容器:

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

    【讨论】:

    • 非常酷!顺便提一句。这甚至使std::transform 成为std::copy
    • 你能解释一下这个构造函数是如何工作的吗?模板的用途是什么?
    • @BartekPL 这是std::tuple 的典型布局。此模板称为parameter packstd::tie 构造一个引用元组,使赋值成为可能。
    【解决方案2】:

    这是一个提取指定数据成员数量的非侵入式版本(即不接触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); });
    

    【讨论】:

    • 是的,但我需要找出传递第三个模板参数的最佳方法
    • @lubgr:这更接近我的愿望。特别是,它是一个可重用的函数,它不假设一种特殊的对象。很好的解决方案。是否可以将std::transform 替换为std::copy
    • 不,std::copy 不起作用,因为 is 正在发生转换,您从另一种类型构造一种类型,当使用目标类型外部的函数(模板)。
    • 好的。我只是因为在@Stack Danny 的解决方案中,可以用std::copy 替换std::transform。但是你的解决方案是一个完全不同的故事。
    • 为什么不使用std::apply()
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2020-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多