Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide the complex range type of a range-v3?

I need a class with a method that returns some kind of range using the range-v3 library. In order to implement such a class I can write it everything right in the definition of that class. For example:

#include <iostream>
#include <set>
#include <range/v3/view/transform.hpp>

class Alpha {
public:
  int x;
};

class Beta : public Alpha {};

class Foo {
public:
  std::set<Alpha*> s;

  auto r() { return s | ranges::v3::view::transform([](Alpha* a) { return static_cast<Beta*>(a); }) }
};

However, in my real case the Foo::r function is quite complex and I would like to hide its implementation. In particular, the implementation make uses of some additional libraries that otherwise do not need to be included when the class Foo is declared.

However, when the definition of Foo::r is separated from its declaration, its return type must be specified explicitly. decltype comes with some help:

Header file(s):

class Foo {
public:
  std::set<Alpha*> s;

  using RangeReturn = decltype(std::declval<std::set<Alpha*>&>() | ranges::v3::view::transform(std::function<Beta*(Alpha*)>()));
  RangeReturn r();
};

Implementation, cpp file:

#include "Foo.h"

Foo::RangeReturn Foo::r() {
    return s | ranges::v3::view::transform(std::function<Beta*(Alpha*)>{
      [](Alpha* a) { return static_cast<Beta*>(a); }
      });
}

This does the immediate job of hiding the actual implementation of Foo::r. However, the type of the return value still effectively "leaks" the information on how the range is constructed. What is probably worse, I am now forced to explicitly use an std::function object in the range pipeline.

But is that all information really needed by the user of the returned range? All the user of Foo::r cares is that it is some kind of iterable. It has:

  • begin() giving an iterator to the beginning of the range
  • end() giving some iterator or sentinel
  • Iterator can be incremented, to iterate over the range
  • Iterator can be dereferenced, giving some type T (Beta* in the example case).

The user does not care that there is a transform view or not, nor the number of transformations, filters and whatnot.

So, my question is -- is there a way to hide all that information? I would like to be able to write something like this:

In the header:

class Foo {
public:
  std::set<Alpha*> s;

  Iterable<Beta*> r();
};

In the cpp file:

#include "Foo.h"

Iterable<Beta*> Foo::r() {
    return s | ranges::v3::view::transform([](Alpha* a) { return static_cast<Beta*>(a); });
}

I can accept the fact that the produced Iterable type may contain real function calls that cannot be inlined due to the hiding process. A link-time optimisation may or may not be able to optimise it later.

Unfortunately, to my knowledge, such Iterable is merely a concept and not a separate type in the library.

like image 311
CygnusX1 Avatar asked Dec 07 '18 15:12

CygnusX1


1 Answers

The return type of r() you are looking for is ranges::v3::any_view<Beta*>.

Note that it applies type erasure that implies some runtime performance penalty which might be significant. Relevant discussion: poor performance of type-erased views.

Live demo: https://wandbox.org/permlink/JylKIHD0NaQsRXdB

like image 141
Daniel Langr Avatar answered Sep 25 '22 03:09

Daniel Langr