Jacob Steinebronn
Jacob Steinebronn

Reputation: 863

Initialize a struct from elements of a vector

I was wondering if it was possible to use a vector as the initializer list for a vector. So, if I have

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

So, the user might input 2 or 3 strings when get_vector_from_user() is called. However, I know that the programmer will always template queryUser and that there will be the same number of elements in vec as strings in the Dataholder template. Is it possible to initialize a struct with the members of a vector? Thanks!

Upvotes: 2

Views: 353

Answers (2)

HolyBlackCat
HolyBlackCat

Reputation: 96246

Yes, it's possible. Enumerating structure members requires complex templates, and possibly code generation because you might need boilerplate code for each specific number of structure members. Luckily there's a library that does it. Behold Boost.PFR, aka magic_get.

Its interface mimics that of std::tuple, so it's easy to use.

The two major limitations are:

  • Your structs must be aggregates (i.e. can't have custom constructors, and more)
  • There is no way to get member names

You will also need a compile-time for loop:

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

Now you can do this:

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

Upvotes: 3

Azam Bham
Azam Bham

Reputation: 1399

For the class in question, write a constructor which accepts a std::vector, or include the logic directly in the function template:

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

Just make sure you add a check to assert there are correct number of elements in the vector.

Upvotes: 4

Related Questions