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!
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.
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.
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 )
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With