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