1

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?

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
user1709708
  • 1,557
  • 2
  • 14
  • 27
  • 1
    related/dupe on what the proxy should look like: https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops – NathanOliver Jul 22 '19 at 19:59

1 Answers1

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_;
};
L. F.
  • 19,445
  • 8
  • 48
  • 82
  • The proxy is pretty much what I've been coming up with but so far I couldn't get it to work. Because to retrieve the proxy via const references or pointers to C I have to make auto data() const (assuming C++20 now). This then means that data_ will also be a const ref within data(), typename R deduces to its underlying type and then I'm trying to construct R& range from a const reference passed to the Proxy ctor. Everything else breaks const-correctness. – user1709708 Jul 24 '19 at 13:45
  • @user1709708 I added the const overload to the answer. Is it what you are looking for? – L. F. Jul 24 '19 at 13:51
  • more or less yes, but I can't get this to compile if I use the Proxy object from above (at least not preserving const-correctness). See here https://ideone.com/XE9RUm – user1709708 Jul 29 '19 at 11:04
  • @user1709708 You need to use `Proxy{data_}` for the const overload. – L. F. Jul 29 '19 at 11:05
  • I see. I think the problem with my actual code was that I didn't use auto as return type for the Proxy's begin/end functions but specified the iterator type. That then again led to constness problems as I didn't make it depend on R's constness. Anyway, everything works as expected now, thanks a lot. – user1709708 Jul 29 '19 at 13:29