Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I elegantly fill/initialize two (or more) ranges from a range containing pairs/tuples of elements?

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

like image 711
rubenvb Avatar asked Nov 21 '25 21:11

rubenvb


1 Answers

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;
}
like image 92
l'L'l Avatar answered Nov 24 '25 12:11

l'L'l