元组的统一初始化

26
今天,我遇到了一个问题,我有一个元组向量,其中元组可能包含多个条目。现在,我想将我的元组向量转换为对象向量,使得元组的条目与对象的统一初始化完全匹配。
下面的代码可以完成任务,但它有点笨拙。我在想是否可能推导出通用解决方案,如果元组恰好与对象的统一初始化顺序相匹配,则可以构造对象。
当要传递的参数数量增加时,这可能是非常理想的功能。
#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

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), ...


非常酷!顺便说一下,这甚至使std::transform成为了一个std::copy - Aleph0
你能解释一下这个构造函数是如何工作的吗?还有模板是用来做什么的? - BartekPL
4
这是一个典型的std::tuple布局。这个模板被称为“参数包”(parameter pack)。std::tie构建了一个引用元组,使得赋值成为可能。 - Stack Danny

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

是的,但我需要找出传递第三个模板参数的最佳方法。 - lubgr
@lubgr:这更接近我所期望的。特别是,它是一个可重用的函数,不假设特定类型的对象。非常好的解决方案。是否可能用std::copy替换std::transform - Aleph0
3
不,std::copy不能使用,因为确实发生了一种转换,你需要从另一个类型构造一个类型,并且当使用目标类型外部的函数(模板)时,无法进行隐式转换。 - lubgr
好的。我只是因为在@Stack Danny的解决方案中,可以用std::copy替换std::transform。但你的解决方案完全不同。 - Aleph0
1
为什么不使用 std::apply() - Deduplicator

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接