Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++20 when should use `iterator_traits<I>::value_type` and when should I use `iter_value_t`?

C++20 concepts adds an alternative way to access iterator traits. For example iter_value_t<I> gives a similar result as iterator_traits<I>::value_type.I have noticed that iterator_traits do not appear to work in concept constraints. iter_value_t works for concepts, but it also works everywhere else. So my hunch is I should use the newer iter_value_t since it works in both contexts.

Is this correct? When should I prefer one or the other?

EDIT: I was missing typename when using iterator_traits in concept constraints. The concepts gave me the jitters!

like image 526
Justin Meiners Avatar asked Aug 21 '21 19:08

Justin Meiners


People also ask

What is Iterator_traits?

iterator_traits are used within algorithms to create local variables of either the type pointed to by the iterator or of the iterator's distance type. The traits also improve the efficiency of algorithms by making use of knowledge about basic iterator categories provided by the iterator_category member.

What is iterator characteristics?

std::iterator_traits is the type trait class that provides uniform interface to the properties of LegacyIterator types. This makes it possible to implement algorithms only in terms of iterators.

What is Difference_type?

difference_type - a type that can be used to identify distance between iterators. value_type - the type of the values that can be obtained by dereferencing the iterator. This type is void for output iterators. pointer - defines a pointer to the type iterated over ( value_type )


2 Answers

You should always use std::iter_value_t<I>.

First of all, simply consider the length:

std::iter_value_t<I>

vs.

typename std::iterator_traits<I>::value_type

Because value_type is a dependent type, you'll need that extra typename keyword, so this is a mouthful.

Secondly, the former is simply defined in more cases than the latter. You'll typically be taking your iterators by value, so it's probably not a huge issue that you'll run into, but std::iter_value_t<int*&> is still int while std::iterator_traits<int*&>::value_type is not defined. Same for std::iterator_traits<int* const>::value_type. That is, std::iterator_traits<I>::value_type works but std::iterator_traits<I const>::value_type or std::iterator_traits<I&>::value_type do not -- that's more that you'll have to write to ensure that you're passing a non-reference, non-cv-qualified type.

There's also a few small other edge cases, like how std::iter_value_t<std::shared_ptr<int>> is int but std::iterator_traits<std::shared_ptr<int>>::value_type is not defined. So if you're writing an algorithm that takes an arbitrary indirectly readable that isn't necessarily an iterator, you can still use it.

So just use the short thing that always works, instead of the much longer thing that sometimes doesn't.


Typically, std::iter_reference_t<I> is much more useful than std::iter_value_t<I>, too.

like image 115
Barry Avatar answered Nov 15 '22 00:11

Barry


iter_value_t<I> is used to implement algorithms in terms of indirectly readable types. iterator_traits<I> is used to implement algorithms in terms of iterators.

Indirectly readable types are types that can be read by applying operator*. That includes pointers, smart pointers and iterators. All such types satisfy indirectly_readable concept.

To fully understand the idea behind the iter_value_t<I> we need to take a look at it's implementation.

If std::iterator_traits<std::remove_cvref_t<T>> is not specialized, then std::iter_value_t<T> is std::indirectly_readable_traits<std::remove_cvref_t<T>>::value_type. Otherwise, it is std::iterator_traits<std::remove_cvref_t<T>>::value_type.

You can see that if it is possible it tries to default to iterator_traits but additionally applies remove_cvref_t transformation to the types. This allows it to work with const-volatile-reference qualified types such as const char* const or const char*& etc.

Is this correct?

No, iterator_traits<I> work with concepts as well (unless I misunderstood what you've meant by that).

#include <vector>
#include <iostream>
#include <concepts>
#include <type_traits>
#include <iterator>

template<class T>
concept my_iterator_concept = 
    std::is_same_v<typename std::iterator_traits<T>::value_type, int>;

int main()
{
    std::vector<int> v;
    std::cout << std::boolalpha << my_iterator_concept<typename decltype(v)::iterator>;
}

Run the code here.

When should I prefer one or the other?

Unless (unlikely) you have a specific need for some iterator_traits features, use iter_value_t<T> together with other members of it's family such as iter_reference_t or iter_difference_t.

Here is an example why you should prefer it. This is a deduction guide for basic_string view.

template<class It, class End>
basic_string_view(It, End) -> basic_string_view<iter_value_t<It>>;

You might want to pass just a const char* const as an alternative to iterators like so:

// Requires c++ 20
#include <string_view>
#include <iostream>

int main()
{
    const char* const c = "example";
    auto str = std::string_view(c, c + 3);
    std::cout << str;
}

iterator_traits<I>::value_type would fail in this case. Run the code here.

The concept of indirectly readable traits and iterator traits are explained next to each other in the standard and can be found in (c++23 n4892 working draft):

23.3.2.2 Indirectly readable traits

23.3.2.3 Iterator traits

like image 45
Marcin Poloczek Avatar answered Nov 14 '22 22:11

Marcin Poloczek