How to provide multiple begin/end proxies for a class

一笑奈何 提交于 2019-12-08 04:58:58

问题


Given the classes

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

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

I'd like to implement something in class Foo so that I can do the following:

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
}

However, I don't wanna expose the class internals by just returning references to the containers. I might also work with classes where the range I'd like to iterate over isn't implemented as a container. So I basically want to create some sort of proxy object which is just a little more than a wrapper to a begin/end pair so that I can iterate over multiple things inside my class. Additionally I would like it to be const correct as displayed above. Is there any well-known pattern to realize this?


回答1:


Consider three cases.


If you want to give full access to your internal data, just make a function to return it: (simply making the member public is also an option)

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

If you want to give read-only access to your internal data, just drop the non-const overload:

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

If you want to give element-only access to your internal data, i.e., you have mutable access to each individual element (when the C itself is non-const), but you can't change the container itself (e.g., insert a new element), you need to return a proxy. Since C++20, we can return a 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_;
};

You can use the Ranges library if C++20 is not available. This way, the user can access the individual elements, but cannot change the container itself.

Alternatively, you can write your own (minimalist) proxy:

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;
};

Then you can return Proxy{data_}:

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

Prior to C++17, you can write it like this without class template argument deduction:

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


来源:https://stackoverflow.com/questions/57152866/how-to-provide-multiple-begin-end-proxies-for-a-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!