For zipping ranges together, we have std::views::zip and friends.
Is there a way to "unzip" a range, i.e. write the following "index filter" function as a range expression?
auto getVerticesAndNormalsForIndices(const std::vector<int>& indices, const std::vector<Point3D>& allVertices, const std::vector<Point3D>& allNormals)
{
std::vector<Point3D> vertices(indices.size());
std::vector<Point3D> normals(vertices.size());
std::ranges::transform(indices, std::views::zip(vertices, normals).begin(), [&allVertices, &allNormals](const size_t i)
{
return std::pair{ allVertices[i], allNormals[i] };
});
return { std::move(vertices), std::move(normals) };
}
I know I could build a view of the normals and vertices, and then use two separate calls to std::views::elements, followed by a call to std::ranges::to to extract a vector, but that holds a lot of repetition. Is there a Standard way of doing this type of "unzip" or is there a way to write an integrated way of unzipping containers (maybe assigning to a zip_view?).
While the Standard Library has nice tools for zipping ranges, there's no built-in “unzip” view.
One possible solution is to write a single-pass function (similar to Marek R's linked example) that fills two output containers simultaneously in a single pass. The example below uses indexing and templating and avoids repetitive separate view pipelines.
#include <iostream>
#include <vector>
#include <utility>
#include <algorithm>
#include <stdexcept>
struct Point3D {
int x;
};
std::ostream& operator<<(std::ostream& os, const Point3D& point) {
return os << point.x;
}
std::ostream& operator<<(std::ostream& os, const std::vector<Point3D>& vec) {
os << "[ ";
for (const auto& p : vec)
os << p << " ";
return os << "]";
}
template<typename Container>
[[nodiscard]] auto getVerticesAndNormalsForIndices(
const std::vector<int>& indices,
const Container& allVertices,
const Container& allNormals)
{
if (allVertices.size() != allNormals.size())
throw std::invalid_argument("Vertices and normals must have the same size");
using ValueType = typename Container::value_type;
std::vector<ValueType> vertices(indices.size());
std::vector<ValueType> normals(indices.size());
for (size_t i = 0; i < indices.size(); ++i) {
int idx = indices[i];
if (idx < 0 || static_cast<size_t>(idx) >= allVertices.size())
throw std::out_of_range("Index out of range");
vertices[i] = allVertices[idx];
normals[i] = allNormals[idx];
}
return std::pair{ std::move(vertices), std::move(normals) };
}
int main() {
std::vector<Point3D> allVertices = { {1}, {2}, {3}, {4} };
std::vector<Point3D> allNormals = { {5}, {6}, {7}, {8} };
std::vector<int> indices = { 2, 0, 1 };
auto [vertices, normals] = getVerticesAndNormalsForIndices(indices, allVertices, allNormals);
std::cout << "Vertices: " << vertices << "\nNormals: " << normals << "\n";
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With