1

In C#, I can do the following

class MyClass {
   private List<int> lst;
   public IEnumerable<int> Numbers { get { return lst; } }
}

This has the effect of giving the public readonly access to lst, without exposing the List member variable to the public. E.g., users of the class can write

foreach (var n in myClass.Numbers) { /* do something with the values * / };

yet they cannot change the list or traverse it in reverse order. This gives me the possibility to change the implementation later without breaking code using the class.

I'd like to do something similar in C++:

class MyClass {
   std::vector<int> lst;
public:
   /* ???? */ getNumbers () const { return /* ??? */ lst; }
};

// I want to be able to write the following
auto rng = myClass.getNumbers ();
auto b = begin (rng);
auto e = end (rng);
// now b and e should be readonly (!) forward (!) iterators

I do not care about compile-time dependencies, i.e. I do not want to use any_range. Yet I do want to make clear in my code that I only guarantee readonly forward behaviour.

JohnB
  • 13,315
  • 4
  • 38
  • 65
  • For those of us not familiar with C#, please describe what `IEnumerable` is, and what your C# sample does. – Benjamin Lindley Dec 30 '14 at 22:30
  • 3
    There isn't a parallel explicit data structure. C++ used the idea of iterators with compile time polymorphic (templates) to get similar functionality. So just return `const std::vector&` and std::begin and std::end should work as expected. – IdeaHat Dec 30 '14 at 22:32
  • Actually JohnB could just return an iterator range - ie, a pair of iterators giving begin and end, or better, a boost::iterator_range with forward iterators - and thus hide the underlying container implementation. – Timo Geusch Dec 30 '14 at 23:22
  • 1
    Just a note here to say, hopefully correctly, that returning an IEnumerable does not mean that the underlying list cannot be changed or traversed in reverse order. A user could cast Numbers to a List and then change it that way. Also, IEnumerable comes with a Reverse method. Check out [AsReadOnly](http://msdn.microsoft.com/en-us/library/e78dcd75%28v=vs.110%29.aspx) for the first issue. – peterjb Dec 30 '14 at 23:33
  • @Timo Geusch: Thank you for the suggestion. That's pretty much what I'd like to do. Where, however, do I get the forward iterators from? – JohnB Dec 30 '14 at 23:33
  • 1
    Probable duplicate of http://stackoverflow.com/q/8764643/103167 ? – Ben Voigt Dec 31 '14 at 01:11

2 Answers2

2

You could derive an iterator type

template <typename Base>
struct const_forward_iterator : public Base {
    using base_type         = Base;
    using iterator_type     = const_forward_iterator;
    using iterator_category = std::forward_iterator_tag;

    const_forward_iterator& operator+=(typename Base::difference_type) = delete;
    const_forward_iterator& operator-=(typename Base::difference_type) = delete;
    const_forward_iterator  operator+ (typename Base::difference_type) = delete;
    const_forward_iterator  operator- (typename Base::difference_type) = delete;

    const_forward_iterator(base_type it) : base_type(it) {}
};

And base a simple range off it

template <typename it>
struct range : std::pair<it,it> {
    range(it b, it e) : std::pair<it,it>(b,e) {}

    it begin() const { return this->first; }
    it end()   const { return this->second; }
};

And profit:

using iterator_type = const_forward_iterator<std::vector<int>::const_iterator>;
range<iterator_type> getNumbers() const { return { lst.cbegin(), lst.cend() }; }

Live On Coliru

#include <vector>
#include <iterator>

namespace detail {
    template <typename Base>
    struct const_forward_iterator : public Base {
        using base_type         = Base;
        using iterator_type     = const_forward_iterator;
        using iterator_category = std::forward_iterator_tag;

        const_forward_iterator& operator+=(typename Base::difference_type) = delete;
        const_forward_iterator& operator-=(typename Base::difference_type) = delete;
        const_forward_iterator  operator+ (typename Base::difference_type) = delete;
        const_forward_iterator  operator- (typename Base::difference_type) = delete;

        const_forward_iterator(base_type it) : base_type(it) {}
    };

    template <typename it>
    struct range : std::pair<it,it> {
        range(it b, it e) : std::pair<it,it>(b,e) {}

        it begin() const { return this->first; }
        it end()   const { return this->second; }
    };
}

class MyClass {
    std::vector<int> lst { 1, 3, 77, 42 };
  public:

    using iterator_type = detail::const_forward_iterator<std::vector<int>::const_iterator>;

    detail::range<iterator_type> getNumbers() const { return { lst.cbegin(), lst.cend() }; }
};

#include <iostream>

int main()
{
    MyClass o;

    for (auto i : o.getNumbers())
        std::cout << i << " ";

    auto numbers = o.getNumbers();
    //numbers.first += 2; error: use of deleted function
}

Outputs

1 3 77 42 

Of course you can factor the code so you can reuse bits, but this was intended as a selfcontained PoC

sehe
  • 374,641
  • 47
  • 450
  • 633
1

As I've found out now, boost::range is the closest C++ equivalent to IEnumerable.

JohnB
  • 13,315
  • 4
  • 38
  • 65