I have a vector of a class Planters
which contain vectors of Plant
s. My goal is to return a vector of plants containing the plants from planter 1, followed by plants from planter 2, etc.
example: planter{{1,2,3,4}, {2,3,4,5}}
should lead to {1,2,3,4,2,3,4,5}
. note that the numbers represent plant objects. I'm trying to use join_view
to flatten it but I am getting the error
error: class template argument deduction failed:
18 | plants = std::ranges::join_view(planterView);
| ^
/home/parallels/CMPT373/se-basic-cpp-template/lib/solutions/task05.cpp:18:52: error: no matching function for call to ‘join_view(std::ranges::ref_view<std::vector<ex4::Planter> >&)’
I have tried the following :
for (auto it : planters){
plants.insert(plants.end(), it.getPlants().begin(), it.getPlants().end());
}
This works however I am only allowed to use one loop (excluding loops inside STL function calls) and can only allocate memory once. The above approach allocates memory multiple times. How do I approach this?
my code:
std::vector<Plant> task05(std::vector<Planter> planters){
std::vector<Plant> plants;
auto planterView = std::views::all(planters);
std::views::transform(planterView, [](Planter planter){ return planter.getPlants();});
plants = ranges::views::all(std::ranges::join_view(planterView));
return plants;
}
Class:
struct Plant {
int plantiness = 0;
Plant(int plantiness)
: plantiness{plantiness}
{ }
bool
operator==(const Plant& other) const noexcept {
return plantiness == other.plantiness;
}
};
class Planter {
public:
Planter(std::initializer_list<Plant> plants)
: plants{plants}
{ }
const std::vector<Plant>&
getPlants() const noexcept {
return plants;
}
private:
std::vector<Plant> plants;
};
std::vector<Plant> task05(std::vector<Planter> planters)
{
auto plants = planters
| std::views::transform([](Planter const& planter) -> decltype(auto) { return planter.getPlants();})
| std::views::join
| std::views::common
;
return std::vector<Plant>(plants.begin(), plants.end());
}
https://godbolt.org/z/T75anE15c
Firstly you go wrong here
auto planterView = std::views::all(planters);
std::views::transform(planterView, [](Planter planter){ return planter.getPlants();});
std::transform
is inplace but views::transform
is not and returns a view which you ignore, but this needs to be passed into join_view
.
Secondly, begin and end return different types, but the constructor of std::vector
needs these to have the same type, so you have to use views::common
to achieve this.
Thirdly, the deduced return type of the lamdba doesn't propagate the const&
-ness of getPlants
resulting in a copy. You have declare the return type as decltype(auto)
.
Also, you don't need to call views::all
. It will be automatically called when calling transform
and you should use the join
adapter rather than the join_view
type.
Also, ranges are very ergonomic when used in a pipeline so just writing one expression spanning multiple lines is best practice.
Also, there's not much point wrapping int
and std::vector<int>
in structs. It's easy just to use std::tuple
, std::array
, std::variant
, std::optional
and never actually write your own types.