11

I have three std::vectors

std::vector<std::string> vec1{ "one","two","three" };
std::vector<std::string> vec2{ "1","2","3" };
std::vector<std::string> vec3{ "i","ii","iii" };

and a struct

struct MyData {
  std::string str1;
  std::string str2;
  std::string str3;
};

I need to obtain a std::vector<MyData>, which is filled from the data of the three vectors:

std::vector<MyData> myData;
myData.reserve(vec1.size());
for (int i = 0; i < vec1.size(); ++i) {
  myData.emplace_back(vec1[i],vec2[i],vec3[i]);
}

Is there a more elegant way instead of using this for-loop (like using views or similar) for C+20? It is guaranteed that the three vectors are of same length.

2
  • why do you have three vectors in the first place? You can use en.cppreference.com/w/cpp/ranges/zip_view.html Commented yesterday
  • 2
    "a more elegant way" is subjective. "How to do it with views" would be ok though Commented yesterday

4 Answers 4

21

You can use views::zip_transform (C++23) like:

auto to_MyData = [](auto... elems)  { return MyData(elems...); };
auto myData = std::views::zip_transform(to_MyData, vec1, vec2, vec3)
            | std::ranges::to<std::vector>();
Sign up to request clarification or add additional context in comments.

1 Comment

Can this approach be combined with std::make_from_tuple<MyData> as suggested in the answer by @pptaszni?
14

Since you mentioned views, you might use C++23 zip_view:

    for (auto [str1, str2, str3]: std::views::zip(vec1, vec2, vec3)) {
        myData.emplace_back(str1, str2, str3);
    }

If explicitly writing 0,1,2 is still too ugly, you can construct your structure directly from tuple:

    for (const auto& elem : std::views::zip(vec1, vec2, vec3)) {
        myData.emplace_back(std::make_from_tuple<MyData>(elem));
    }

Comments

5

If you have to use c++20, you could define a view that iterates MyData objects from your vectors, e.g.

auto vw = std::views::iota(size_t{0},vec1.size())
  | std::views::transform ([&] (auto i) {
    return MyData {vec1[i],vec2[i],vec3[i]};
 });

where you use std::views::iota in order to iterate all the required indexes, and then you can create on the fly a MyStruct object through the std::views::transform function. Note that the provided functor needs to access the vectors, so you have to use a reference &.

You might notice that this is not a great deal compared to your initial approach. However, you have some improvement since you can iterate over the objects rather than having to create a container (a std::vector for instance) and then iterate it. When possible, it is always a Good Thing to iterate rather than having an explicit container that may duplicate information. You could write for instance

for (auto [x, y, z] : vw) {
  std::cout << x << " " << y << " " << z << '\n';
}

without the need to create a full-fledged std::vector<MyData>. Of course, you can still fill a vector from such a view.

Comments

4

You can use ranges for this:

std::vector<MyData> myData =
        std::views::zip(vec1, vec2, vec3) |
        std::ranges::views::transform([](const auto& t) {
            return MyData{get<0>(t), get<1>(t), get<2>(t)};
        }) |
        std::ranges::to<std::vector>();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.