I would like to hide a vector field in my class but allow easy iteration through its elements but nothing else. So that class's client would be able to do
for (auto element : foo.getElements()) { }
but not
foo.getElements()[42];
Is there some simple way of achieving this w/o creating new confusing types?
I cannot say what is and is not a "new confusing type". But this is sufficient for the needs of a range-based for:
template<typename Iter>
class iterator_range
{
public:
  iterator_range(Iter beg, Iter end) : beg_(beg), end_(end) {}
  Iter begin() const {return beg_;}
  Iter end() const {return end_;}
private:
  Iter beg_, end_;
};
The Range TS adds more complexity to what constitutes a "range", but this is good enough for range-based for. So your foo.getElements function would look like this:
auto getElements()
{
  return iterator_range<vector<T>::iterator>(vec.begin(), vec.end());
}
auto getElements() const
{
  return iterator_range<vector<T>::const_iterator>(vec.begin(), vec.end());
};
                        You can use an higher-order function to only expose iteration functionality:
class something
{
private:
    std::vector<item> _items;
public:
    template <typename F>
    void for_items(F&& f)
    {
        for(auto& i : _items) f(i);
    }
};
Usage:
something x;
x.for_items([](auto& item){ /* ... */ });
The advantages of this pattern are:
std::vector to something else without breaking the user.To be completely correct and pedantic, you have to expose three different ref-qualified versions of for_items. E.g.:
template <typename F>
void for_items(F&& f) &      { for(auto& i : items) f(i); }
template <typename F>
void for_items(F&& f) const& { for(const auto& i : items) f(i); }
template <typename F>
void for_items(F&& f) &&     { for(auto& i : items) f(std::move(i)); }
The above code ensures const-correctness and allows elements to be moved when the something instance is a temporary.
Here is a proxy-based approach (though I'm not sure whether the new type meets the requirement of not being confusing).
template<class Container> class IterateOnlyProxy {
    public:
        IterateOnlyProxy(Container& c) : c(c) {}
        typename Container::iterator begin() { return c.begin(); }
        typename Container::iterator end() { return c.end(); }
    private:
        Container& c;
};
The proxy is used as a return type for the getElements() method,
class Foo {
    public:
        using Vec = std::vector<int>;
        using Proxy = IterateOnlyProxy<Vec>;
        Proxy& getElements() { return elementsProxy; }
    private:
        Vec elements{4, 5, 6, 7};
        Proxy elementsProxy{elements};
};
and client code can iterate over the underlying container, but that's about it.
Foo foo;
for (auto element : foo.getElements())
    std::cout << element << std::endl;
foo.getElements()[42]; // error: no match for ‘operator[]’
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With