Reputation: 24092
Having the following struct MyStruct
:
struct MyStruct {
explicit MyStruct(std::string name) : name_(name){}
std::string name() const { return name_; }
private:
std::string name_;
};
I would like to transform a std::vector
of MyStruct
s to a std::vector<std::string>
. I have managed to do it like so:
#include <iostream>
#include <string>
#include <vector>
#include <boost/range/adaptor/transformed.hpp>
struct MyStruct {
explicit MyStruct(std::string name) : name_(name){}
std::string name() const { return name_; }
private:
std::string name_;
};
int main() {
std::vector<MyStruct> vm;
vm.emplace_back("asd");
vm.emplace_back("qwe");
vm.emplace_back("zxc");
using namespace boost::adaptors;
auto vs = vm | transformed([](const MyStruct& c){return c.name();});
for (const auto& c : vs) std::cout << c << std::endl;
}
but then vs
is not a vector but a:
const boost::range_detail::transformed_range<(lambda at /tmp/main.cpp:21:36), std::vector<MyStruct, std::allocator<MyStruct> > > => const boost::range_detail::transformed_range<(lambda at /tmp/main.cpp:21:36), std::vector<MyStruct, std::allocator<MyStruct> > >
How can I achieve this? Preferably I would like to do it while initializing the vector - not by declaring vector and then std::copy
or something similar.
I know that I can do something similar to:
std::vector<std::string> vv;
vv.reserve(boost::size(vs));
boost::copy(vs, std::back_inserter(vv));
but I'd like a one step initialization (preferably with a const
qualifier).
I believe this is some sort of a high order (functional) map function that I need or an std/boost/handwritten equivalent.
Upvotes: 3
Views: 357
Reputation: 21156
Not as nice as solution based on ranges-v3, but it has the advantage to not need any 3rd party library:
const auto vs = [&]{
std::vector<std::string> t(mv.size());
std::transform(mv.begin(), mv.end(), t.begin(), [](auto& s) {return s.name(); });
return t;
}();
You can of course use reserve
and back_inserter
to prevent default construction, but I'd actually time the code before prefering one over the other.
Upvotes: 0
Reputation: 206577
You can also create a helper class that acts as wrapper around std::vector::iterator
that can be used to construct a std::vector<std::string>
from a std::vector<MyStruct>
.
#include <iostream>
#include <string>
#include <vector>
struct MyStruct {
explicit MyStruct(std::string name) : name_(name){}
std::string name() const { return name_; }
private:
std::string name_;
};
// A minimal wrapper around std::vector::iterator
// to help with constructing a std::vector<std::string> from a
// std::vector<MyStruct>
struct Iter
{
using iterator_category = std::input_iterator_tag;
using value_type = std::string;
using pointer = std::string*;
using reference = std::string;
using difference_type = long;
Iter(std::vector<MyStruct>::iterator iter) : iter_(iter) {}
std::string operator*() const { return (*iter_).name(); }
bool operator!=(Iter const& rhs) const { return this->iter_ != rhs.iter_; }
Iter& operator++() { ++iter_; return *this;}
std::vector<MyStruct>::iterator iter_;
};
int main() {
std::vector<MyStruct> vm;
vm.emplace_back("asd");
vm.emplace_back("qwe");
vm.emplace_back("zxc");
std::vector<std::string> vs(Iter(vm.begin()), Iter(vm.end()));
for (const auto& c : vs) std::cout << c << std::endl;
}
Upvotes: 1
Reputation: 303017
I'd recommend using range-v3
, which is basically the new and improved Boost.Ranges. There, it's just:
std::vector<std::string> vs = vm | ranges::view::transform(&MyStruct::name);
Note that if you captured this by auto
, you wouldn't get a vector
. The resulting object has a conversion operator.
With Boost.Ranges, you can't do that natively, but you can write your own piped adaptor:
struct make_vector_t
{
template <class R>
friend auto operator|(R&& range, make_vector_t)
{
using std::begin;
using std::end;
using value_type = typename iterator_traits<decltype(begin(range))>::value_type;
return std::vector<value_type>(begin(range), end(range));
}
} constexpr make_vector{};
and just use it:
auto vs = vm
| transformed([](const MyStruct& c){return c.name();});
| make_vector;
Upvotes: 5
Reputation: 424
If range-v3
as Barry mentioend is not an option, why not just wrap your multi-line algorithm into a function call? E.g.
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
struct MyStruct {
MyStruct(std::string n) : m_name(n) {}
std::string name() const { return m_name; }
std::string m_name;
};
template <typename T> auto ExtractNames(const std::vector<T> &vm) {
std::vector<decltype(std::declval<T>().name())> result(vm.size());
std::transform(vm.begin(), vm.end(), result.begin(),
[](const T &v) { return v.name(); });
return result;
}
int main() {
std::vector<MyStruct> vm{{"asd"}, {"qwe"}, {"zxc"}};
auto vs = ExtractNames(vm);
for (auto &elem : vs) {
std::cout << elem << "\n";
}
return 0;
}
Upvotes: 0