Is it possible to create a list of values of member fields using iterators?

Viewed 78

Consider the following struct

struct Pair 
{
   int a;
   std::string b;
};

I have a std::vector<Pair> pairs but I want a std::vector<std::string> listOfBFromPairs which contains all the values of b from the list of pairs.

I would like to do this in a one-liner.

I have used OpenGL before and I have previously created a struct, specified a starting point

pairs.begin() + offsetof(Pair, b)

and given a stride length (the size of the pair object in memory) and the number of things I want to parse and it has done what I am after here.

Is this possible here too using c++ iterators?

The specific use case I have is I want to make an unordered set of Vulkan extension names but Vulkan returns structs which contain the extension name as a member. I will then use the unordered set to check against extensions my application would like to use.

2 Answers

I want a std::vector<std::string> listOfBFromPairs which contains all the values of b from the list of pairs.

I would like to do this in a one-liner?

Sure you can. You can use std::transform from <algorithm> header like this

#include <algorithm> // std::transform
#include <iterator>  // std::back_inserter

std::vector<Pair> pairs;
std::vector<std::string> listOfBFromPairs;
listOfBFromPairs.reserve(pairs.size()); // reserve the memory for unwanted reallocations

std::transform(pairs.begin(), pairs.end(),
   std::back_inserter(listOfBFromPairs), [](const Pair& pair) { return pair.b; }
);

However in case you need listOfBFromPairs with no duplicates using unordered_set, you need to create a std::unordered_set<std::string> listOfBFromPairs; not a vector of strings. In that case, you need to insert the strings to unordered_set using std::inserter.

#include <unordered_set>
#include <algorithm> // std::transform
#include <iterator>  // std::inserter

std::vector<Pair> pairs;
std::unordered_set<std::string> listOfBFromPairs;

std::transform(pairs.begin(), pairs.end(),
   std::inserter(listOfBFromPairs, listOfBFromPairs.end()), [](const auto& pair) { return pair.b; }
);

As you asked for a one-liner, here's a Boost approach (not very readable IMHO):

#include <boost/range/adaptor/transformed.hpp>
#include <functional>

const std::vector<Pair> src;

const auto listOfBFromPairs = boost::copy_range<std::vector<std::string>>(src | boost::adaptors::transformed(std::mem_fn(&Pair::b)));

If the target container is e.g. a std::unordered_set, change the last line to

const auto listOfBFromPairs = boost::copy_range<std::unordered_set<std::string>>(src | boost::adaptors::transformed(std::mem_fn(&Pair::b)));

(where only the instantiation of copy_range changed). You can achieve something similar with C++20 ranges.

Related