c++c++20stl-algorithmstd-ranges

Flatten vector of classes which contain vectors of structs


I have a vector of a class Planters which contain vectors of Plants. 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;
};

Solution

  • 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.