Как предоставить несколько начальных/конечных прокси для класса

Учитывая классы

struct Data
{
  void bar() const;
  void baz();
}

class Foo
{
  std::vector<Data> data;
  std::map<size_t, Data> indexed_data;
}

Я хотел бы реализовать что-то в классе Foo, чтобы я мог сделать следующее:

int main()
{
  Foo foo;

  for(const auto& data : foo.data())
    data.bar();

  for(auto& data : foo.indexed_data())
    data.baz();

  const auto& foo_ref = foo;
  for(auto& data : foo_ref.data())
    data.baz();  // constness violated, shouldn't compile
}

Однако я не хочу раскрывать внутренности класса, просто возвращая ссылки на контейнеры. Я также мог бы работать с классами, в которых диапазон, который я хотел бы перебрать, не реализован в виде контейнера. Итак, я в основном хочу создать какой-то прокси-объект, который будет чуть больше, чем оболочка для пары начала/конца, чтобы я мог перебирать несколько вещей внутри своего класса. Кроме того, я хотел бы, чтобы он был правильным, как показано выше. Есть ли какой-нибудь известный шаблон для реализации этого?


person user1709708    schedule 22.07.2019    source источник
comment
related/dupe о том, как должен выглядеть прокси-сервер: for-loops" title="как заставить мой пользовательский тип работать с диапазоном на основе циклов for"> stackoverflow.com/questions/8164567/   -  person NathanOliver    schedule 22.07.2019


Ответы (1)


Рассмотрим три случая.


Если вы хотите предоставить полный доступ к своим внутренним данным, просто создайте функцию для их возврата: (также можно просто сделать член общедоступным)

class C {
public:
          Type& data()       { return data_; }
    const Type& data() const { return data_; }
private:
    Type data_;
};

Если вы хотите предоставить доступ только для чтения к своим внутренним данным, просто отбросьте неконстантную перегрузку:

class C {
public:
    const Type& data() const { return data_; }
private:
    Type data_;
};

Если вы хотите предоставить доступ только к элементам к своим внутренним данным, т. е. у вас есть изменяемый доступ к каждому отдельному элементу (когда сам C неконстантный), но вы не можете изменить сам контейнер (например, вставить новый элемент), вам нужно вернуть прокси. Начиная с C++20, мы можем вернуть std::ranges::ref_view:

class C {
public:
    auto data()       { return std::ranges::ref_view(data_); }
    auto data() const { return std::ranges::ref_view(data_); }
private:
    Type data_;
};

Вы можете использовать библиотеку Ranges, если C++20 недоступен. Таким образом, пользователь может получить доступ к отдельным элементам, но не может изменить сам контейнер.

В качестве альтернативы вы можете написать свой собственный (минималистский) прокси:

template <typename R>
class Proxy {
public:
    explicit Proxy(R& r) :range{r} {}
    auto begin() const { return range.begin(); }
    auto   end() const { return range.end(); }
private:
    R& range;
};

Затем вы можете вернуть Proxy{data_}:

class C {
public:
    auto data()       { return Proxy{data_}; }
    auto data() const { return Proxy{data_}; }
private:
    Type data_;
};

До C++17 вы могли написать это так, без вывода аргументов шаблона класса:

class C {
public:
    auto data()       { return Proxy<      Type>{data_}; }
    auto data() const { return Proxy<const Type>{data_}; }
private:
    Type data_;
};
person L. F.    schedule 23.07.2019
comment
Прокси - это в значительной степени то, что я придумал, но до сих пор я не мог заставить его работать. Потому что для получения прокси-сервера через константные ссылки или указатели на C я должен сделать auto data() const (предполагая, что сейчас C++20). Затем это означает, что data_ также будет константной ссылкой внутри data(), typename R выводит его базовый тип, а затем я пытаюсь создать диапазон R& из константной ссылки, переданной в Proxy ctor. Все остальное нарушает const-корректность. - person user1709708; 24.07.2019
comment
@user1709708 user1709708 Я добавил к ответу перегрузку const. Это то, что вы ищете? - person L. F.; 24.07.2019
comment
более или менее да, но я не могу заставить это скомпилировать, если я использую объект Proxy сверху (по крайней мере, не сохраняя константную правильность). См. здесь ideone.com/XE9RUm - person user1709708; 29.07.2019
comment
@user1709708 user1709708 Вам нужно использовать Proxy<const Type>{data_} для перегрузки константы. - person L. F.; 29.07.2019
comment
Я понимаю. Я думаю, что проблема с моим фактическим кодом заключалась в том, что я не использовал auto в качестве возвращаемого типа для функций начала/конца прокси, а указал тип итератора. Это снова привело к проблемам с константностью, поскольку я не зависел от константности R. В любом случае, теперь все работает, как и ожидалось, большое спасибо. - person user1709708; 29.07.2019