Rostislav Rostislav - 2 months ago 11
C++ Question

array_view alternative for maps, sets, etc

Let's assume I have some class hierarchy that has a couple of

virtual
functions returning a container reference:

#include <vector>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>

class Interface {
public:
virtual const std::vector<int>& getArray() const = 0;
virtual const std::set<int>& getSet() const = 0;
virtual const std::map<int, int>& getMap() const = 0;
};

class SubclassA : public Interface {
public:
const std::vector<int>& getArray() const override { return _vector; }
const std::set<int>& getSet() const override { return _set; }
const std::map<int, int>& getMap() const override { return _map; }

private:
std::vector<int> _vector;
std::set<int> _set;
std::map<int, int> _map;
};


At the moment, it is only possible to actually return a
vector
,
set
, or
map
in any subclass of the
Interface
class. However, for the
vector
part, I could use, e.g., a
gsl::array_view
to soften this restriction:

class Interface {
public:
virtual gsl::array_view<const int> getArray() const = 0;
virtual const std::set<int>& getSet() const = 0;
virtual const std::map<int, int>& getMap() const = 0;
};

class SubclassA : public Interface {
public:
gsl::array_view<const int> getArray() const override { return _vector; }
const std::set<int>& getSet() const override { return _set; }
const std::map<int, int>& getMap() const override { return _map; }

private:
std::vector<int> _vector;
std::set<int> _set;
std::map<int, int> _map;
};

class SubclassB : public Interface {
public:
gsl::array_view<const int> getArray() const override { return _array; }
// const std::set<int>& getSet() const override { return _set; }
// const std::map<int, int>& getMap() const { return _map; }

private:
std::array<int, 3> _array;
std::unordered_set<int> _set;
std::unordered_map<int, int> _map;
};


So the question is, is there an alternative for an
array_view
for use with other container types? Basically all I would like to have is a lightweight object that I could return from a function that would act as an immutable view to some container without specifying a specific container type. It would even make sense to me to shove a
std::set
to something like an
array_view
, but with fewer supported operations (e.g., no random access).
map
is clearly a different beast and would require a different
view
supporting associative lookup, but even for a
map
I think it would be useful to have the ability to say
array_view<const std::pair<const int, int>>
. Am I asking for too much? Or perhaps there are reasonable ways to implement this? Or maybe there are even existing implementations of such 'views'?

PS: inheritance is not a prerequisite - I just thought that it's the easiest way to present the problem.

Answer

If you're just looking for a type-erased range, you could check out boost::any_range:

using IntRange = boost::any_range<
                     int,
                     boost::forward_traversal_tag,
                     int,
                     std::ptrdiff_t>;

int sum(IntRange const& range) {
    return std::accumulate(range.begin(), range.end(), 0);
}

int main()
{
    std::cout << sum(std::vector<int>{1, 2, 3}) << std::endl;  // OK, 6
    std::cout << sum(std::set<int>{4, 5, 6}) << std::endl;     // OK, 15
}

Even when you try to misuse it:

sum(std::map<int, int>{})

the error message isn't terrible:

/usr/local/include/boost/range/detail/any_iterator_wrapper.hpp:40:60: error: invalid static_cast from type 'std::pair<const int, int>' to type 'int&'
             return static_cast<Reference>(const_cast<T&>(x));
                                                            ^

You could create an alias for your use case:

template <typename T>
using FwdImmutableRangeT = boost::any_range<T,
                               boost::forward_traversal_tag,
                               const T&, std::ptrdiff_t>;

And return those:

class Interface {
public:
    virtual FwdImmutableRange<int> getArray() const = 0;
    virtual FwdImmutableRange<const int> getSet() const = 0;
    virtual FwdImmutableRange<std::pair<const int, int>> getMap() const = 0;
};