5

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;

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • 4
    There is no such thing as "some iterator that only defined `value_type`". If it only defined `value_type` it isn't an iterator. – T.C. Mar 13 '18 at 16:22
  • 4
    What T.C. said. If it doesn't define all those things, it isn't an iterator, and so `iterator_traits` should not be used to enquire about it. If you want a "get its value_type" trait then write that, don't misuse `iterator_traits`. It really doesn't make sense for `std::iterator_traits>::value_type` to be valid. – Jonathan Wakely Mar 13 '18 at 16:25
  • 4
    As I understand it, the purpose of `iterator_traits` is both to facilitate the usage of iterator-related types _and_ to ensure that the given type is a proper iterator, and so the requirement of having all the type definitions is meant to avoid accidental construction of partially defined `iterator_traits` with types that just happen to have some member type called `difference_type`/`value_type`/etc... – jdehesa Mar 13 '18 at 16:27
  • Exactly. For example, `iterator_traits>::difference_type` and `iterator_traits>::value_type`, but `std::atomic` is not an iterator. – Jonathan Wakely Mar 13 '18 at 16:29
  • @T.C. Iterators are defined by the operations it supports, aren't they? Pointers as iterators are by your definition anomalies, when they should be a special (not general) case. – Passer By Mar 14 '18 at 06:25
  • @jdehesa I disagree. That is the exact opposite purpose of traits. Traits provide a uniform interface to type aliases, hence the existence of pointer specialization _and_ the allowance of user-defined specialization. The user-defined specialization is there _specifically_ because an iterator need not have all type aliases defined to be useful in a generic context through traits – Passer By Mar 14 '18 at 06:28
  • @JonathanWakely The above, and note how `std::allocator_traits` does what you claim shouldn't happen. As long as `value_type` is defined, `std::allocator_traits` will have everything else defaulted to something if none is present – Passer By Mar 14 '18 at 06:31
  • My point being, traits are supposed to be used to treat types equally, so why restrict it? I would've thought traits are supposed to solve the need to specialize for certain types (like the last example in post) – Passer By Mar 14 '18 at 06:42
  • Don't use `allocator_traits` as an example of good anything, it exists to correct historical mistakes, and provide backward compatibility after large changes to the allocator requirements which would have made all existing allocator classes non-conforming. – Jonathan Wakely Mar 14 '18 at 07:22
  • `iterator_traits` works with pointers because there is a partial specialization. `iterator_traits` works with iterator classes because they provide the required typedefs. It *doesn't* work with arbitrary types because they are not iterators. Why on earth would you want `iterator_traits>` to have any members? It's worth noting that the SFINAE-friendliness was added later, originally using it with types that aren't iterators was just undefined. – Jonathan Wakely Mar 14 '18 at 07:31

1 Answers1

1

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137