在 C++20 中,什么时候应该使用 `iterator_traits<I>::value_type` 什么时候应该使用 `iter_value_t`?

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

C++20 概念添加了一个 alternative way 来访问迭代器特征。例如 iter_value_t<I> 给出了与 iterator_traits<I>::value_type 相似的结果。 我注意到 iterator_traits 似乎在概念约束中不起作用。 iter_value_t 适用于概念,但它也适用于其他任何地方。 所以我的直觉是我应该使用更新的 iter_value_t 因为它在两种情况下都有效。

这是正确的吗?我应该在什么时候选择其中之一?

编辑: 在概念约束中使用 iterator_traits 时我丢失了 typename。这些概念让我感到不安!

iter_value_t<I>用于根据间接可读类型实现算法。 iterator_traits<I> 用于根据迭代器实现算法。

间接可读类型是可以通过应用 operator* 读取的类型。这包括指针、智能指针和迭代器。所有这些类型都满足 indirectly_readable 概念。

要完全理解 iter_value_t<I> 背后的想法,我们需要看一下它的实现。

如果std::iterator_traits<std::remove_cvref_t<T>>没有特化,那么std::iter_value_t<T>就是std::indirectly_readable_traits<std::remove_cvref_t<T>>::value_type。否则就是std::iterator_traits<std::remove_cvref_t<T>>::value_type.

您可以看到,如果可能的话,它会尝试默认为 iterator_traits,但还会对类型应用 remove_cvref_t 转换。这允许它使用 const-volatile-reference 限定类型,例如 const char* constconst char*&

Is this correct?

不,iterator_traits<I> 也处理概念(除非我误解了你的意思)。

#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>;
}

运行代码here.

When should I prefer one or the other?

除非(不太可能)您对某些 iterator_traits 功能有特定需求,否则请将 iter_value_t<T> 与其家族的其他成员一起使用,例如 iter_reference_t or iter_difference_t.

这是一个您应该更喜欢它的示例。这是一个deduction guide for basic_string view.

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

您可能只想传递一个 const char* const 作为迭代器的替代方法,如下所示:

// 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 在这种情况下会失败。 运行 代码 here.

间接可读特征和迭代器特征的概念在标准中并排解释,可以在 (c++23 n4892 working draft):

中找到

23.3.2.2 Indirectly readable traits

23.3.2.3 Iterator traits

您应该始终使用 std::iter_value_t<I>

首先简单考虑一下长度:

std::iter_value_t<I>

对比

typename std::iterator_traits<I>::value_type

因为 value_type 是一个依赖类型,你需要那个额外的 typename 关键字,所以这是 一口.

其次,前者简单定义的情况比后者多。您通常会按值获取迭代器,因此 运行 可能不是一个大问题,但 std::iter_value_t<int*&> 仍然是 intstd::iterator_traits<int*&>::value_type 不是定义。 std::iterator_traits<int* const>::value_type 也一样。也就是说,std::iterator_traits<I>::value_type 有效,但 std::iterator_traits<I const>::value_typestd::iterator_traits<I&>::value_type 无效——您必须编写更多内容以确保您传递的是非引用、非 cv-合格类型。

还有其他一些小的边缘情况,例如 std::iter_value_t<std::shared_ptr<int>>intstd::iterator_traits<std::shared_ptr<int>>::value_type 未定义。因此,如果您正在编写一种算法,该算法接受一个不一定是迭代器的任意间接可读对象,您仍然可以使用它。

所以只使用总是有效的短的东西,而不是有时不起作用的更长的东西。


通常,std::iter_reference_t<I> 也比 std::iter_value_t<I> 有用得多。