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.
Related
The following code compiles in C++11, C++14, and C++17, but does not compile in C++20. What change to the standard broke this code?
#include <vector>
#include <utility>
template<typename T>
struct bar
{
typename T::reverse_iterator x;
};
struct foo
{
bar<std::vector<std::pair<foo, foo>*>> x;
};
int main()
{
foo f;
}
The error is quite long, but can be summarized as:
template argument must be a complete class
This was always undefined. [res.on.functions]/2.5 says:
In particular, the effects are undefined in the following cases:
[...]
If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
std::pair does not (and cannot) support incomplete types. You were just relying on order of instantiation to kind of get around that. Something changed in the library that slightly changed the evaluation order, leading to the error. But undefined behavior is undefined - it happened to work before and it happens to not work now.
As to why it's specifically C++20 that is causing this to fail. In C++20, iterators changed to have this new iterator_concept idea. In order to instantiate that, reverse_iterator needs to determine what the concept should be. That looks like this:
#if __cplusplus > 201703L && __cpp_lib_concepts
using iterator_concept
= conditional_t<random_access_iterator<_Iterator>,
random_access_iterator_tag,
bidirectional_iterator_tag>;
using iterator_category
= __detail::__clamp_iter_cat<typename __traits_type::iterator_category,
random_access_iterator_tag>;
#endif
Now, in the process of checking random_access_iterator, the root of iterator concept hierarchy is wonderfully named input_or_output_iterator, specified in [iterator.concept.iterator]:
template<class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> can-reference;
} &&
weakly_incrementable<I>;
So, we have to do *i on our iterator type. That's __gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> > , in this case. Now, *i triggers ADL - because of course it does. And ADL requires instantiation of all the associated types - because those associated types might have injected friends that could be candidates!
This, in turn, requires instantiating pair<foo, foo> - because, we have to check. And then that ultimately fails in this specific case because instantiating a type requires instantiating all of the type's special member functions, and the way that libstdc++ implements conditional assignment for std::pair is using Eric Fisellier's trick:
_GLIBCXX20_CONSTEXPR pair&
operator=(typename conditional<
__and_<is_copy_assignable<_T1>,
is_copy_assignable<_T2>>::value,
const pair&, const __nonesuch&>::type __p)
{
first = __p.first;
second = __p.second;
return *this;
}
And is_copy_assignable requires complete types and we don't have one.
But really even if pair used concepts to check in this case, that would still involve instantiating the same type traits, so we'd ultimately end up in the same position.
Moral of the story is, undefined behavior is undefined.
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.
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.
Template class std::iterator is set to be deprecated in C++17. Why so? It has been a handy way to make sure std::iterator_traits works, especially if you can make use of the default template arguments. Is there some other way of doing it in C++17?
From the proposal that suggested its deprecation:
As an aid to writing iterator classes, the original standard library supplied the iterator class template to automate the declaration of the five typedefs expected of every iterator by iterator_traits. This was then used in the library itself, for instance in the specification of std::ostream_iterator:
template <class T, class charT = char, class traits = char_traits<charT> >
class ostream_iterator:
public iterator<output_iterator_tag, void, void, void, void>;
The long sequence of void arguments is much less clear to the reader than simply providing the expected typedefs in the class definition itself, which is the approach taken by the current working draft, following the pattern set in C++14 where we deprecated the derivation throughout the library of functors from unary_function and binary_function.
In addition to the reduced clarity, the iterator template also lays a trap for the unwary, as in typical usage it will be a dependent base class, which means it will not be looking into during name lookup from within the class or its member functions. This leads to surprised users trying to understand why the following simple usage does not work:
#include <iterator>
template <typename T>
struct MyIterator : std::iterator<std::random_access_iterator_tag, T> {
value_type data; // Error: value_type is not found by name lookup
// ... implementations details elided ...
};
The reason of clarity alone was sufficient to persuade the LWG to update the standard library specification to no longer mandate the standard iterator adapators as deriving from std::iterator, so there is no further use of this template within the standard itself. Therefore, it looks like a strong candidate for deprecation.
You can also see STL's reasoning in LWG 2438. (h/t T.C.)
As for some other way of doing it, not really. You could basically implement your own version of std::iterator (which isn't too hard) or manually write out all of those typedefs (which isn't too hard either, and I actually prefer it for clarity).
As Barry states, the working group has decided that explicitly declaring the types in the class is more readable and leads to less surprises than inheriting from std::iterator.
It's not too hard, though, to convert to the explicit types (below example taken from www.fluentcpp.com here). Given a class that was declared like so:
class MyIterator
: public std::iterator<std::forward_iterator_tag, int, int, int*, int&>
{
...
The class without std::iterator becomes:
class MyIterator
{
public:
using iterator_category = std::forward_iterator_tag;
using value_type = int;
using difference_type = int;
using pointer = int*;
using reference = int&;
// ...
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.