i want to initialize a const std::vector<int>
member variable in the initializer list of a constructor, given a std::vector<std::tuple<int, float>>
constructor argument. The vector should contain all the first tuple items.
Is there a one-liner that extracts an std::vector<int>
containing all the first tuple entries from an std::vector<std::tuple<int, float>>
?
CodePudding user response:
With C 20 ranges:
struct X
{
const std::vector<int> v;
template <std::ranges::range R>
requires std::convertible_to<std::ranges::range_value_t<R>, int>
X(R r)
: v{r.begin(), r.end()}
{}
X(const std::vector<std::tuple<int, float>>& vt)
: X{vt | std::ranges::views::elements<0>}
{}
};
With ranges-v3:
struct X
{
const std::vector<int> v;
X(const std::vector<std::tuple<int, float>>& vt)
: v{vt | ranges::views::transform([] (auto t) {
return std::get<0>(t); })
| ranges::to<std::vector>()}
{}
};
And a Frankenstein's monster:
#include <ranges>
#include <range/v3/range/conversion.hpp>
struct X
{
const std::vector<int> v;
X(const std::vector<std::tuple<int, float>>& vt)
: v{vt | std::ranges::views::elements<0>
| ranges::to<std::vector>()}
{}
};
CodePudding user response:
Not a one-liner to setup, but certainly one to use - you can write a function to do the conversion, and then the constructor can call that function when initializing the vector member, eg:
std::vector<int> convertVec(const std::vector<std::tuple<int, float>> &arg)
{
std::vector<int> vec;
vec.reserve(arg.size());
std::transform(arg.begin(), arg.end(), std::back_inserter(vec),
[](const std::tuple<int, float> &t){ return std::get<int>(t); }
);
return vec;
}
struct Test
{
const std::vector<int> vec;
Test(const std::vector<std::tuple<int, float>> &arg)
: vec(convertVec(arg))
{
}
};
CodePudding user response:
Adding to @bolov's answer, let's talk about what you might have liked to do, but can't in a one-liner.
There's the
to<>()
function from ranges-v3 from @bolov 's answer - it materializes a range into an actual container (a range is lazily-evaluated, and when you create it you don't actually iterate over the elements). There is no reason it shouldn't be in the standard library, if you ask me - maybe they'll add it 2023?You may be wondering - why do I need that
to()
? Can't I just initialize a vector by a range? And the answer is - sort of, sometimes, maybe. Consider this program:#include <vector> #include <tuple> #include <ranges> void foo() { using pair_type = std::tuple<int, double>; std::vector<pair_type> tvec { {12, 3.4}, {56, 7.8}, { 90, 91.2} }; auto tv = std::ranges::transform_view( tvec, [](const pair_type& p) { return std::get<0>(p);} ); std::vector<int> vec1 { tv.begin(), tv.end() }; std::vector<std::tuple<int>> vec2 { std::ranges::transform_view{ tvec, [](const pair_type& p) { return std::get<0>(p);} }.begin(), std::ranges::transform_view{ tvec, [](const pair_type& p) { return std::get<0>(p);} }.end() }; }
The
vec1
statement will compile just fine, but thevec2
statement will fail (GodBolt.org). This is surprising (to me anyway)!You may also be wondering why you even need to go through ranges at all. Why isn't there a...
template <typename Container, typename UnaryOperation> auto transform(const Container& container, UnaryOperation op);
which constructs the transformed container as its return value? That would allow you to write:
std::vector<int> vec3 { transform( tvec, [](const pair_type& p) { return std::get<0>(p); } ) }
... and Bob's your uncle. Well, we just don't have functions in the C standard library which take containers. It's either iterator pairs, which is the "classic" pre-C 20 standard library, or ranges.
Now, the way I've declared the
transform()
function, it is actually tricky/impossible to implement generally, since you don't have a way of converting the type of a container to the same type of container but with a different element. So, instead, let's write something a little easier:template < typename T, template <typename> class Container, typename UnaryOperation > auto transform(const Container<T>& container, UnaryOperation op) { auto tv = std::ranges::transform_view(container, op); using op_result = decltype(op(*container.begin())); return Container<op_result> { tv.begin(), tv.end() }; }
and this works (GodBolt.org).