Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

From a vector of structs, get a vector that collects one of the fields for every struct

Tags:

c++

Let's say I have the following struct:

struct Point {
    double X,Y,Z;
};

and the following vector:

std::vector<Point> v;
// populate v with random points

Now, I want to call something like collect(v, X) and get a std::vector that contains the X values from the original struct vector in it, for example:

v.push_back(Point{1.0, 2.0,  3.0});
v.push_back(Point{1.1, 0.0, -0.5});
auto ans = collect(v,X);
// ans = [1.0, 1.1]

I think this is a pretty common task, and I'm sure there's a good name for this that I couldn't come up with while asking (feel free to point me out!).

I can do this:

std::vector<double> collectX(std::vector<Point> v) {
    std::vector<double> output;
    for (auto elem : v) {
        output.push_back(elem.X);
    }
}
/* Repeat for each field the struct Point has... */

I know C++ has no reflection. I was wondering if there's a workaround for this? As you may imagine, the struct I am working with doesn't have just 3 fields, so writing a method for each fields is a bit daunting and inelegant.

like image 926
Adrian Avatar asked Feb 26 '19 06:02

Adrian


2 Answers

Use std::transform, std::back_inserter, and std::mem_fn:

#include <functional>
//...
std::vector<Point> v{{0,1,2},{9,8,7}};

std::vector<double> x;

std::transform(v.begin(), v.end(), std::back_inserter(x),
           std::mem_fn(&Point::x));

Compilers can typically optimize away the indirection behind std::mem_fn.

like image 143
j6t Avatar answered Nov 18 '22 10:11

j6t


so writing a method for each fields is a bit daunting and inelegant

An immediate fix for that is to pass the field identifier as an argument too.

std::vector<double> collect(double Point::* f, std::vector<Point> const& v) {
    std::vector<double> output;
    for (auto const& elem : v) {
        output.push_back(elem.*f);
    }
    return output;
}

To be called like this:

collect(&Point::X, v);

If the types aren't always double, then the above can easily be made a template over the member type:

template<typename T>
std::vector<T> collect(T Point::* f, std::vector<Point> const& v) {
    std::vector<T> output;
    for (auto const& elem : v) {
        output.push_back(elem.*f);
    }
    return output;
}

And finally, the term you are looking for this sort of extraction is "projection". I.e, what one gets when projecting a function onto an axis, very roughly speaking. In our case, the function maps an index of the vector to a Point, and the projection is onto the x axis, as it were.

It can also be written on the fly with the C++ standard library, or with the ranges-v3 library. Projection is a very common operation with ranges of items, so many range-centric libraries will have the facilities to do it.

like image 8
StoryTeller - Unslander Monica Avatar answered Nov 18 '22 10:11

StoryTeller - Unslander Monica