I found that typename gsl::span<const gsl::byte>::const_iterator doesn't satisfy Readable concept in range-v3. After looking through the concept, I found this constraint:
template<typename I>
auto requires_(I&&) -> decltype(
concepts::valid_expr(
// The value, reference and rvalue reference types are related
// through the CommonReference concept.
concepts::model_of<CommonReference, reference_t<I> &&, value_t<I> &>(),
concepts::model_of<CommonReference, reference_t<I> &&, rvalue_reference_t<I> &&>(),
concepts::model_of<CommonReference, rvalue_reference_t<I> &&, value_t<I> const &>(),
// Experimental additional tests. If nothing else, this is a good workout
// for the common_reference code.
concepts::model_of<Same, ranges::common_reference_t<reference_t<I>, value_t<I>>, value_t<I>>(),
concepts::model_of<Same, ranges::common_reference_t<rvalue_reference_t<I>, value_t<I>>, value_t<I>>()
));
ranges::common_reference_t removes the const from the value_type and then they are not same.
What do the CommonReference constraints mean? Why should Readable satisfy them?
Your problem is in the GSL. From the source for span_iterator (https://github.com/Microsoft/GSL/blob/master/gsl/span#L145-L147):
using value_type =
std::conditional_t<IsConst, std::add_const_t<typename Span::element_type>,
typename Span::element_type>;
So span::const_iterator has a const-qualified value_type. That's weird and wrong. It's probably also not standard conforming. I have yet to find definitive proof within the standard for that assertion, but the standard is highly suggestive. For instance, here is the specialization of std::iterator_traits for pointers to const:
template<class T> struct iterator_traits<const T*> {
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = const T*;
using reference = const T&;
using iterator_category = random_access_iterator_tag;
};
See? value_type is not const-qualified, even for pointers-to-const.
Related
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.
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>>{} );
I use ostreambuf_iterator as the case below:
Before c++17,
template< class CharT, class Traits = std::char_traits<CharT> >
class ostreambuf_iterator : public std::iterator<std::output_iterator_tag,
void, void, void, void>
requires us to determinate every parameter type of std::iterator, so, for std::ostreambuf_iterator, void is not so bad.
As we know, std::iterator deprecated in c++17. So, iterators, should typedef their member types inside their own body, for example:
Member type Definition
---------------------------------------------
value_type void
difference_type void
pointer void
reference void
iterator_category std::output_iterator_tag
Question:
Why should these void types still be typedefed? IOW, I think
Member type Definition
---------------------------------------------
iterator_category std::output_iterator_tag
is enough. After all, the motivations for deprecating std::iterator is just to simplify the standard(library). So there should be some reasons that I cannot figure out.
After googling, I found a question about on stack overflow, and the answer said:
Defining it as a void, on the other hand, can prevent errors like:
typename Iter::value_type v = *it; //useless with an output iterator if it compiled
But I think if we don't typedef value_type, such errors can also be prevented.
Because ISO C++17 std::iteartor_traits needs all the 5 of these member types in general, even if it is already SFINAE-friendly since C++17. If there are insufficient nested types, the instance of std::iterator_traits will have no members desired. Currently, this is not changed in the current draft.
See also iterator_traits SFINAE friendliness for rationale of the original proposal.
Consider the following code:
#include <initializer_list>
#include <vector>
auto cref_lambda = [] (const auto& il){
using T= typename decltype(il)::value_type;
};
auto cval_lambda = [] (const auto il){
using T=typename decltype(il)::value_type;
};
int main(){
std::initializer_list<int> il;
cref_lambda(il);
cval_lambda(il);
}
cref_lambda does not compile because we are trying to :: into a reference.
I am aware of the workarounds(using std::remove_reference_t or just using decltype(*il.begin());) but I wonder if there is a better idiom to use here.
The way to resolve your problem at hand is to add std::decay_t to the decltype instruction. From cppreference:
Applies lvalue-to-rvalue, array-to-pointer, and function-to-pointer implicit conversions to the type T, removes cv-qualifiers, and defines the resulting type as the member typedef type.
Most importantly, it acts as the identity for a type which is not qualified according to any of the above annotations. Hence it is safe to write
using T = typename std::decay_t<decltype(il)>::value_type;
to get the unqualified value_type, independent of the the function signature.
Now to the other part of your question, how to write this shorter. Well, in the case of your example one could say, that since your lambda does not capture anything it could also be replaced by a free function template.
template < typename T >
void cref(std::initializer_list<T> const &il) {
/* use T and il */
}
or if it should work for any container
template < typename U >
void cref(U const &il) {
using T = typename U::value_type;
/* use T and il */
}
The clear advantage of the first case is, that you get access to T = value_type “for free“. Another advantage (in my opinion) is that you will get a much clearer compiler error should you accidentally call this function with something that is not a std::initializer_list<T>. You could remedy this shortcoming of the lambda by adding a static_assert but that would further strain the “shortness” which you initially wanted to find.
Lastly, if you really like the lambda style of writing functions or you have to capture something and cannot use the free function approach, you might want to consider using the GCC extension for template lambdas:
auto cref_lambda = [] <typename U> (U const &il){
using T = typename U::value_type;
};
That's probably the shortest you can get.
When reading the excerpt from cppreference
If Iterator does not have the five member types difference_type, value_type, pointer, reference, and iterator_category, then this template has no members by any of those names (std::iterator_traits is SFINAE-friendly)
I automatically thought it meant each member type is defined when they are defined in the iterator itself. But lo and behold, it actually meant if all five are defined, then they are defined.
struct defined
{
using difference_type = int;
using value_type = int;
using pointer = int*;
using reference = int&;
using iterator_category = std::input_iterator_tag;
};
struct undefined
{
using value_type = int;
};
template<typename T>
using value_type = typename std::iterator_traits<T>::value_type;
void foo()
{
using std::experimental::is_detected_v;
static_assert(is_detected_v<value_type, defined>);
static_assert(!is_detected_v<value_type, undefined>);
}
Live
Why is this? I would've thought it is friendlier if they were independent of each other. For example if an algorithm just needs to store the value_type somewhere and doesn't care about anything else.
template<typename It>
auto amazingfy(It first, It last)
{
typename std::iterator_traits<It>::value_type v;
for(; first != last; first++)
v += *first;
return v;
}
It will fail to compile on some iterator that only defined value_type, but funnily enough, succeed if it were instead typename It::value_type v;
Some insight can be gathered from corresponding proposal N3844:
With benefit of hindsight, it has from time to time been argued that
the SGI STL (and consequently C++98) erred in specifying
iterator_traits as a bundle of five type aliases, and that individual
iterator-related traits would have been a better design. Even if true,
this paper proposes no change to the basic bundled design, keeping to
an all-or-nothing principle.
So it looks like it was just to try to approach the current situation very cautiously and make the minimum change required to make the traits SFINAE-friendly. Selective inclusion of the member would lead to the half-defined traits, and apparently, this was considered a potentially far-reaching result.
Given:
struct Iter {
using value_type = int;
using difference_type = int;
using reference = int;
using pointer = int;
using iterator_category = int;
};
The following works fine with libstc++, but fails to compile against libc++ 5.0.0:
#include <iterator>
#include <type_traits>
static_assert(
std::is_same<
std::iterator_traits<Iter>::iterator_category,
Iter::iterator_category
>::value, "");
With the error:
error: no member named 'iterator_category' in 'std::__1::iterator_traits<Iter>'
std::is_same<std::iterator_traits<Iter>::iterator_category, Iter::iterator_category>::value, "");
The static assertion succeeds if Iter::iterator_category is one of the standard input categories, e.g. std::input_iterator_tag.
IMHO it shouldn't fail, because the C++ draft states in [iterator.traits]#2:
If Iterator has valid ([temp.deduct]) member types difference_type, value_type, pointer, reference, and iterator_category, iterator_traits<Iterator> shall have the following as publicly accessible members:
using difference_type = typename Iterator::difference_type;
using value_type = typename Iterator::value_type;
using pointer = typename Iterator::pointer;
using reference = typename Iterator::reference;
using iterator_category = typename Iterator::iterator_category;
Otherwise, iterator_traits<Iterator> shall have no members by any of the above names.
Can anybody please explain whether this is an implementation bug, or why my expectations are wrong?
We also have, in [std.iterator.tags]:
It is often desirable for a function template specialization to find out what is the most specific category of its iterator argument, so that the function can select the most efficient algorithm at compile time. To facilitate this, the library introduces category tag classes which are used as compile time tags for algorithm selection. They are: input_iterator_tag, output_iterator_tag, forward_iterator_tag, bidirectional_iterator_tag and random_acces_iterator_tag. For every iterator of type Iterator, iterator_traits<Iterator>::iterator_category shall be defined to be the most specific category tag that describes the iterator's behavior.
namespace std {
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag: public input_iterator_tag { };
struct bidirectional_iterator_tag: public forward_iterator_tag { };
struct random_access_iterator_tag: public bidirectional_iterator_tag { };
}
int isn't one of those tags, so iterator_traits<Iter>::iterator_category can't give you back int. I would suggest that having an invalid iterator category is simply violating the preconditions of iterator_traits - this doesn't necessarily mean that the library must fail, but it also doesn't mean that failure is a library bug.
However, these preconditions aren't spelled out as explicitly in [iterators] as they are in other parts of the library section. So I'd be inclined to suggest that both libraries are correct, but libc++'s approach to not defining any member aliases in iterator_traits<Iter> is likely better.