Search code examples
c++c++20

How to write a const iterator to const std::shared_ptr<const Widget> stored in std::vector<std::shared_ptr<Widget>>?


Suppose I have vector of pointers to objects of class Widget, which are heavy objects with no owner. More often than not, my code needs to iterate over this vector, but needs only access to const Widget.

Is it possible to write the WidgetSet class such that const WidgetSet stores const Widgets? I.e. the iteration over this class yields const std::shared_ptr<const Widget>& objects.

I tried the code below, but came to various issues, highlighted in the comments:

#include <experimental/propagate_const>
#include <memory>
#include <vector>

struct Widget {
   void const_method() const {}
   void modify() {}
};

class WidgetSet {
   std::vector<std::experimental::propagate_const<std::shared_ptr<Widget>>> _widgets;
  public:
   [[nodiscard]] auto begin() const { return _widgets.cbegin(); }
   [[nodiscard]] auto end() const { return _widgets.cend(); }
   [[nodiscard]] auto begin() { return _widgets.begin(); }
   [[nodiscard]] auto end() { return _widgets.end(); }
};

void delegate_modify(const std::shared_ptr<Widget>& w) {
   w->modify();
}

void exposed_delegate_modify(std::experimental::propagate_const<std::shared_ptr<Widget>>& w) {
   //I need to prevent this from compiling and I would not like clients to use types have std::experimental::propagate_const in its name.
   w->modify();
}

void delegate_test(const std::shared_ptr<const Widget>& w) {
   w->const_method();
   // w->modify(); // error, as expected.
}

void test(const WidgetSet cws, WidgetSet ws) {
   for(const auto& w: cws) {
      w->const_method(); // OK
//      w->modify(); // error, as expected.
      delegate_modify(get_underlying(w)); //surprise! this compiles, but shouldn't: w is const!
//      exposed_delegate_modify(w); // error, as expected.
//      delegate_test(w); // error: No matching function for call.
   }
}

I also need this class to interoperate with existing functions that accept std::shared_ptr<const Widget> and std::shared_ptr<Widget> which do not depend on the experimental::propagate_const.


Solution

  • Is it possible to write the WidgetSet class such that const WidgetSet stores const Widgets? I.e. the iteration over this class yields const std::shared_ptr<const Widget>& objects.

    No. const std::shared_ptr<const Widget>& is not an object type, it is a reference type. You can construct a std::shared_ptr<const Widget> from a std::shared_ptr<Widget>, but you don't store it anywhere, so returning a reference to that shared ptr isn't safe, it will dangle at the end of the full expression.

    It is somewhat suspicious to be writing functions that take std::shared_ptr by const reference. Either they are going to participate in the shared ownership, so you should pass shared_ptr values, or they are observing the pointed-to value, so you should pass references, or pointers (if the stored pointers can be null).

    You can write something that unwraps the propogate_const:

    class WidgetSet {
       struct get_mutable {
          Widget& operator()(std::experimental::propagate_const<std::shared_ptr<Widget>>& ptr) const { return *ptr; }
       };
       struct get_const {
          const Widget& operator()(const std::experimental::propagate_const<std::shared_ptr<Widget>>& ptr) const { return *ptr; }
       };
        
       std::vector<std::experimental::propagate_const<std::shared_ptr<Widget>>> _widgets;
       decltype(_widgets | std::views::transform(get_mutable{})) _mutable = _widgets | std::views::transform(get_mutable{});
       decltype(_widgets | std::views::transform(get_const{})) _const = _widgets | std::views::transform(get_const{});
      public:
       [[nodiscard]] auto begin() const { return _const.cbegin(); }
       [[nodiscard]] auto end() const { return _const.cend(); }
       [[nodiscard]] auto begin() { return _mutable.begin(); }
       [[nodiscard]] auto end() { return _mutable.end(); }
    };
    

    Godbolt link with variations for const Widget * and std::shared_ptr<const Widget>