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