Как написать константный итератор для const std::shared_ptr<const Widget>, хранящегося в std::vector<std::shared_ptr<Widget>>?

Предположим, у меня есть вектор указателей на объекты класса Widget, которые являются тяжелыми объектами без владельца. Чаще всего моему коду нужно перебирать этот вектор, но ему нужен только доступ к const Widget.

Можно ли написать класс WidgetSet так, чтобы const WidgetSet хранил const Widget? Т.е. итерация по этому классу дает const std::shared_ptr<const Widget>& объектов.

Я попробовал приведенный ниже код, но столкнулся с различными проблемами, отмеченными в комментариях:

#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.
   }
}

Мне также нужен этот класс для взаимодействия с существующими функциями, которые принимают std::shared_ptr<const Widget> и std::shared_ptr<Widget>, которые не зависят от experimental::propagate_const.

Что касается «Я бы не хотел, чтобы клиенты использовали типы, содержащие std::experimental::propagate_const в названии» — как насчет определения using value_type = std::experimental::propagate_const<std::shared_ptr<Widget>>; и разрешения пользователям использовать это? const WidgetSet::value_type& w выглядит не так уж и плохо, не так ли?

Ted Lyngmo 12.08.2024 15:03

@TedLyngmo Я знаю об этом варианте, но предпочел бы не делать этого. Видите ли, большая часть нашей логики связана с отдельными виджетами, а WidgetSet — это скорее «дополнительная опция». Если указатель на отдельный виджет начнет зависеть от деталей реализации WidgetSet, это запутает всех участников проекта. (В этой проблемной области распространение const для итератора WidgetSet является деталью реализации).

Adam Ryczkowski 12.08.2024 15:44

Я знаю, что обычно это не рекомендуется, но рассматривали ли вы возможность использования вектора Widget* вместоshared_ptr? Это облегчит чтение вашего кода (без propagate_const) и позволит достичь желаемого. Я также собирался предложить продолжать использовать Shared_ptr, но создать итератор, который при разыменовании возвращает Widget* вместо Shared_ptr, но это, вероятно, слишком большая работа.

Mickaël C. Guimarães 12.08.2024 16:04

@MickaëlC.Guimarães, когда вы откладываете std::vector<Widget *>::const_iterator, вы получаете Widget * const, а не const Widget *. У него та же проблема с распространением констант, что и у std::shared_ptr<Widget>

Caleth 12.08.2024 16:08

@MickaëlC.Guimarães У меня не может быть виджета*, потому что виджеты используются совместно с кодом Python через nanobind, и у них нет явного владельца. Мы также не хотим утечек памяти.

Adam Ryczkowski 12.08.2024 16:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Можно ли написать класс WidgetSet так, чтобы const WidgetSet хранил const Widget? Т.е. итерация по этому классу дает const std::shared_ptr<const Widget>& объектов.

Нет. const std::shared_ptr<const Widget>& — это не объектный тип, а ссылочный тип. Вы можете создать std::shared_ptr<const Widget> из std::shared_ptr<Widget>, но вы его нигде не сохраните, поэтому возврат ссылки на этот общий ptr небезопасен, он будет висеть в конце полного выражения.

Несколько подозрительно писать функции, которые принимают std::shared_ptr по константной ссылке. Либо они будут участвовать в совместном владении, поэтому вам следует передавать значения shared_ptr, либо они наблюдают за указанным значением, поэтому вам следует передавать ссылки или указатели (если сохраненные указатели могут быть нулевыми).

Вы можете написать что-то, что разворачивает 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 с вариациями для const Widget * и std::shared_ptr<const Widget>

И это решает мои проблемы! Спасибо @Caleth! Я многому научился у тебя сегодня!

Adam Ryczkowski 12.08.2024 16:32

Другие вопросы по теме