视图迭代器是否在视图的生命周期之后有效?
Are view iterators valid beyond the lifetime of the view?
假设我有一个自定义容器 class 在地图中存储数据:
class Container
{
public:
void add(int key, std::string value) { _data.emplace(key, std::move(value)); }
private:
std::map<int, std::string> _data;
};
我想提供一个接口来访问地图的值(不是键)。范围库提供 std::views::values
给我一个地图值的范围:
auto values() { return std::views::values(_data); }
用法:
Container c;
c.add(1, "a");
c.add(3, "b");
c.add(2, "c");
for (auto &value : c.values())
std::cout << value << " "; // Prints "a c b"
但是因为我想把我的 class 当作一个容器,所以我想要 begin()
和 end()
迭代器。我可以这样做吗?
auto begin() { return std::ranges::begin(values()); }
auto end() { return std::ranges::end(values()); }
我在这里调用 values()
来获取映射值的范围,并获取指向范围开头的迭代器(或哨兵结束迭代器)。但是范围本身超出范围并被破坏。迭代器本身是否仍然有效?
从this example开始,迭代器似乎是有效的。但这是否由标准保证,无论是针对 std::views:values
还是针对一般视图?
Are view iterators valid beyond the lifetime of the view?
这里的属性称为借用范围。如果范围是借用范围,那么即使范围被销毁,它的迭代器仍然有效。 R&
,如果 R
是一个范围,是最普通的借用范围 - 因为它不是迭代器将绑定到的 reference 的生命周期.还有其他几个熟悉的借用范围 - 如 span
和 string_view
.
一些范围适配器是有条件借用的 (P2017)。也就是说,它们不会在它们正在适应的范围之上添加任何额外的状态——因此如果基础范围是(或基础范围是),则可以借用适应的范围。例如,每当借用 r
时,就会借用 views::reverse(r)
。但是 views::split(r, pat)
不是有条件地借用的 - 因为模式存储在适配器本身而不是迭代器中(假设,它也可以存储在迭代器中,但需要付费)。
views::values(r)
就是这样的一个例子:只要 r
被借用,它就是一个借用的范围。并且,在您的示例中,基础范围是 ref_view
,它本身总是被借用的(根据 R&
总是被借用的相同原则)。
这里注意:
auto begin() { return std::ranges::begin(values()); }
auto end() { return std::ranges::end(values()); }
将右值范围传递到 ranges::begin
仅在借用范围时有效。那是 [range.access.begin]/2.1:
If E
is an rvalue and enable_borrowed_range<remove_cv_t<T>>
is false
, ranges::begin(E)
is ill-formed.
因此,因为您的原始代码已编译,所以您可以确定它是有效的。
假设我有一个自定义容器 class 在地图中存储数据:
class Container
{
public:
void add(int key, std::string value) { _data.emplace(key, std::move(value)); }
private:
std::map<int, std::string> _data;
};
我想提供一个接口来访问地图的值(不是键)。范围库提供 std::views::values
给我一个地图值的范围:
auto values() { return std::views::values(_data); }
用法:
Container c;
c.add(1, "a");
c.add(3, "b");
c.add(2, "c");
for (auto &value : c.values())
std::cout << value << " "; // Prints "a c b"
但是因为我想把我的 class 当作一个容器,所以我想要 begin()
和 end()
迭代器。我可以这样做吗?
auto begin() { return std::ranges::begin(values()); }
auto end() { return std::ranges::end(values()); }
我在这里调用 values()
来获取映射值的范围,并获取指向范围开头的迭代器(或哨兵结束迭代器)。但是范围本身超出范围并被破坏。迭代器本身是否仍然有效?
从this example开始,迭代器似乎是有效的。但这是否由标准保证,无论是针对 std::views:values
还是针对一般视图?
Are view iterators valid beyond the lifetime of the view?
这里的属性称为借用范围。如果范围是借用范围,那么即使范围被销毁,它的迭代器仍然有效。 R&
,如果 R
是一个范围,是最普通的借用范围 - 因为它不是迭代器将绑定到的 reference 的生命周期.还有其他几个熟悉的借用范围 - 如 span
和 string_view
.
一些范围适配器是有条件借用的 (P2017)。也就是说,它们不会在它们正在适应的范围之上添加任何额外的状态——因此如果基础范围是(或基础范围是),则可以借用适应的范围。例如,每当借用 r
时,就会借用 views::reverse(r)
。但是 views::split(r, pat)
不是有条件地借用的 - 因为模式存储在适配器本身而不是迭代器中(假设,它也可以存储在迭代器中,但需要付费)。
views::values(r)
就是这样的一个例子:只要 r
被借用,它就是一个借用的范围。并且,在您的示例中,基础范围是 ref_view
,它本身总是被借用的(根据 R&
总是被借用的相同原则)。
这里注意:
auto begin() { return std::ranges::begin(values()); }
auto end() { return std::ranges::end(values()); }
将右值范围传递到 ranges::begin
仅在借用范围时有效。那是 [range.access.begin]/2.1:
If
E
is an rvalue andenable_borrowed_range<remove_cv_t<T>>
isfalse
,ranges::begin(E)
is ill-formed.
因此,因为您的原始代码已编译,所以您可以确定它是有效的。