alfC
alfC

Reputation: 16242

What exactly is the Readable concept in Range v3?

I have coded an iterators-like class and for some reason it doesn’t pass the Readable concept as defined in Range v3. I don’t know why and I am trying to see exactly how I need to modify the syntax (and semantics) to fulfill the concept.

What are the minimum syntactic requirements for an iterator to be Readable according to Range v3? Can that be written a set of statements-that-must-compile? (see example below)

I have an iterator It with which I can do the basic stuff (what I would call "readable"), yet it doesn't pass the concept check:

#include <range/v3/all.hpp>
...
    It i; // ok
    typename It::value_type val = *i; // ok
    typename It::reference ref = *i;   // ok
    typename It::value_type val2{ref}; // ok
    static_assert( ranges::CommonReference<typename It::reference&&, typename It::value_type&>{} ); // ok
    static_assert( ranges::Readable<It>{} ); // error: static assertion failed

What other constructs involving i I can write that it will make obvious that It is not Readable? In other words, what generic code would compile only and only-if the iterator is Range v3-Readable?

In many places, it says "if it behaves like a pointer then is Readable", but I can not find what is wrong with my iterator. I will be able to understand what is wrong when I see what code needs to compile?

I am trying to debug why my iterator fails to fullfil the concept (and therefore is rejected by range v3 functions). Note that is It were std::vector<bool>::iterator it will all work.


The Readable concept code in Range v3 https://ericniebler.github.io/range-v3/structranges_1_1v3_1_1concepts_1_1_readable.html is similar to https://en.cppreference.com/w/cpp/experimental/ranges/iterator/Readable

(I am using version 0.5.0 [Fedora30])

template < class In >

concept bool Readable =
  requires {
    typename ranges::value_type_t<In>;
    typename ranges::reference_t<In>;
    typename ranges::rvalue_reference_t<In>;
  } &&
  CommonReference<ranges::reference_t<In>&&, ranges::value_type_t<In>&> &&
  CommonReference<ranges::reference_t<In>&&, ranges::rvalue_reference_t<In>&&> &&
  CommonReference<ranges::rvalue_reference_t<In>&&, const ranges::value_type_t<In>&>;

So it looks like the iterator must (or can deduce) value_t<It>, extracted from It::value_type, reference_t<It> extracted from It::reference.

I don't know how rvalue_reference_t is deduced or what CommonReference means in terms of contrains to the syntax.

Upvotes: 2

Views: 367

Answers (1)

metalfox
metalfox

Reputation: 6731

For an iterator to model Readable in range-v3, it needs:

  • to be dereferenceable via a meaningful operator *
  • to either have a specialization of readable_traits or have a public member type value_type or element_type defining the associated value type.

The simplest Readable user-defined type I can think of is:

#include <range/v3/all.hpp>

template <typename T>
class It
{
public:
  using value_type = T;

private:
  T x;

public:
  T operator *() const { return x; }
};

static_assert( ranges::Readable<It<int>>{} );

which compiles cleanly with range-v3 version 0.3.5 (https://godbolt.org/z/JMkODj).

In version 1 of range-v3 Readable is no longer a type, but a constexpr value convertible to bool, so that the correct assertion in this case would be:

static_assert( ranges::Readable<It<int>> );

value_type and the value returned by operator * need not to be the same. However, they must be in a sense inter-convertible for the algorithms to work. That's where the CommonReference concept comes into play. This concept basically requires that two types share a “common reference type” to which both can be converted. It essentially delegates to a common_reference type traits, whose behaviour is described in great detail at cppreference (note, however, that what is described there is for the stuff in the ranges TS, which may not be exactly the same as that of the range-v3 library).

In practice, the Readable concept can be satisfied by defining a conversion operator in the type returned by operator *. Here's a simple example that passes the test (https://godbolt.org/z/5KkNpv):

#include <range/v3/all.hpp>

template <typename T>
class It
{
private:
  class Proxy
  {
  private:
    T &x;

  public:
    Proxy(T &x_) : x(x_) {}

    operator T &() const { return x; }
  };

public:
  using value_type = T;

private:
  T x;

public:
  Proxy operator *() { return {x}; }
};

static_assert( ranges::Readable<It<bool>>{} );

Upvotes: 3

Related Questions