Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

encapsulating std::vector to allow iteration but nothing else

Tags:

c++

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?

like image 865
MK. Avatar asked Jun 11 '18 17:06

MK.


3 Answers

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());
};
like image 62
Nicol Bolas Avatar answered Oct 12 '22 13:10

Nicol Bolas


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:

  • Simple to implement (no need for any "proxy" class);
  • Can transparently change 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.

like image 25
Vittorio Romeo Avatar answered Oct 12 '22 13:10

Vittorio Romeo


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[]’
like image 35
lubgr Avatar answered Oct 12 '22 14:10

lubgr