【问题标题】:Initialize a struct from elements of a vector从向量的元素初始化结构
【发布时间】:2021-08-21 22:17:55
【问题描述】:

我想知道是否可以使用向量作为向量的初始化列表。所以,如果我有

struct somedata{
    string str1;
    string str2;
}

struct moredata{
    string str1;
    string str2;
    string str3;
}

template<class Dataholder>
Dataholder queryUser(args){
    auto vec = get_vector_from_user(args)
    Dataholder dat{vec}; // The elements of vec become the structured variables in dat.
    return dat;
}

因此,当调用get_vector_from_user() 时,用户可能会输入 2 或 3 个字符串。但是,我知道程序员将始终以queryUser 为模板,并且vec 中的元素数量与Dataholder 模板中的字符串数量相同。是否可以使用向量的成员初始化结构?谢谢!

【问题讨论】:

  • 编写一个接受向量的构造函数并相应地初始化成员
  • @Slava 好吧,没错,但我想 OP 想要一个内置的语言功能,类似于迭代器对等的向量初始化。
  • 这有点像 C++(与 Java 或 C# 相对)等非反射语言在自动序列化方面的问题。没有内置方法可以“枚举”任意类的成员。
  • 另外,您的数据类的外观让我怀疑您应该在每个类中使用向量或数组;如果你这样做,该语言会为你提供更多支持,以从其他容器初始化构造函数中的元素:容器中的元素 are 可枚举。
  • 为什么get_vector_from_user 返回的是std::vector 而不是std::array

标签: c++ vector struct initialization


【解决方案1】:

对于有问题的类,编写一个接受std::vector的构造函数,或者直接在函数模板中包含逻辑:

struct somedata{
    string str1;
    string str2;
    somedata(const std::vector& vec) : str1(vec[0]), str2(vec[1]){
        
    }
}

struct moredata{
    string str1;
    string str2;
    string str3;
    moredata(const std::vector& vec) : str1(vec[0]), str2(vec[1]), str3(vec[2]){
        
    }
}

template<class Dataholder>
Dataholder queryUser(args){
    auto vec = get_vector_from_user(args)
    Dataholder dat{vec}; // The elements of vec become the structured variables in dat.
    return dat;
}

只需确保添加检查以断言向量中有正确数量的元素。

【讨论】:

    【解决方案2】:

    是的,这是可能的。枚举结构成员需要复杂的模板,并且可能需要代码生成,因为您可能需要针对每个特定数量的结构成员的样板代码。幸运的是,有一个图书馆可以做到这一点。看Boost.PFR,又名magic_get

    它的界面模仿std::tuple,所以很容易使用。

    两个主要限制是:

    • 您的结构必须是聚合(即不能有自定义构造函数等)
    • 无法获取成员名称

    您还需要一个编译时for 循环:

    template <typename Integer, Integer ...I, typename F>
    constexpr void constexpr_for_each(std::integer_sequence<Integer, I...>, F &&func)
    {
        (func(std::integral_constant<Integer, I>{}) , ...);
    }
    
    template <auto N, typename F>
    constexpr void constexpr_for(F &&func)
    {
        if constexpr (N > 0)
            constexpr_for_each(std::make_integer_sequence<decltype(N), N>{}, std::forward<F>(func));
    }
    

    现在你可以这样做了:

    struct A
    {
        std::string x;
        std::string y;
        std::string z;
    };
    
    int main()
    {
        std::vector<std::string> vec = {"a", "b", "c"};
        if (vec.size() != boost::pfr::tuple_size_v<A>)
            throw std::runtime_error("Wrong vector size.");
        A a;
        constexpr_for<boost::pfr::tuple_size_v<A>>([&](auto index)
        {
            constexpr auto i = index.value;
            static_assert(std::is_same_v<std::string, boost::pfr::tuple_element_t<i, A>>);
            boost::pfr::get<i>(a) = vec[i];
        });
    
        std::cout << a.x << ' ' << a.y << ' ' << a.z << '\n'; // a b c
    }
    

    Run on gcc.godbolt.org

    【讨论】:

    • 这太酷了!不幸的是,这样做的目的是为了让我可以使用成员名称而不是硬编码的数组索引,但这是一些非常有用的知识!
    • @JacobSteinebronn ...但是,这正是这个巫术的作用,不是吗?上例中的a 是您问题中的Dataholder 实例dat
    • @JacobSteinebronn 我同意 Ted Lyngmo 的观点,这不正是您要求的吗?您在这里不需要成员名称。除非您想从成员名称中提取数字(假设它们都被命名为strN),并将它们用作索引,忽略实际的成员顺序,但这并没有太大意义,因为您不妨使用数组。答案中的方法忽略名称,并按照声明的顺序使用成员。不需要硬编码索引。
    猜你喜欢
    • 2011-12-25
    • 2014-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    • 2011-11-01
    • 2014-07-06
    相关资源
    最近更新 更多