视图迭代器在视图的生命周期之外是否有效?

Kev*_*vin 11 c++ language-lawyer c++20 std-ranges

假设我有一个将数据存储在地图中的自定义容器类:

class Container
{
  public:
    void add(int key, std::string value) { _data.emplace(key, std::move(value)); }

  private:
    std::map<int, std::string> _data;
};
Run Code Online (Sandbox Code Playgroud)

我想提供一个接口来访问地图的值(而不是键)。范围库提供了std::views::values地图值的范围:

auto values() { return std::views::values(_data); }
Run Code Online (Sandbox Code Playgroud)

用法:

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"
Run Code Online (Sandbox Code Playgroud)

但由于我想将我的类视为一个容器,所以我想要拥有begin()end()迭代器。我可以这样做吗?

auto begin() { return std::ranges::begin(values()); }
auto end() { return std::ranges::end(values()); }
Run Code Online (Sandbox Code Playgroud)

在这里,我调用values()以获取映射值的范围,并获取到范围开头的迭代器(或哨兵结束迭代器)。但范围本身超出范围并被破坏。迭代器本身仍然有效吗?

这个例子来看,迭代器似乎是有效的。std::views:values但是,无论是针对具体观点还是针对一般观点,标准是否保证了这一点?

Bar*_*rry 9

\n

视图迭代器在视图的生命周期之外是否有效?

\n
\n

这里的属性称为借用范围。如果范围是借用范围,则即使范围被销毁,其迭代器仍然有效。R&如果R是一个范围,则它是最简单的借用范围 - 因为它不是迭代器所绑定的引用的生命周期。还有其他几个熟悉的借用范围 - 例如spanstring_view

\n

一些范围适配器是有条件借用的P2017)。也就是说,它们不会在正在调整的范围之上添加任何额外的状态——因此,如果基础范围是(或基础范围是),则可以借用调整后的范围。例如,views::reverse(r)无论何时借用,都是r借用的。但views::split(r, pat)不是有条件借用的 - 因为模式存储在适配器本身中而不是迭代器中(假设,它也可以存储在迭代器中,但需要付出一定的代价)。

\n

views::values(r)就是一个这样的例子:只要r被借用,它就是一个借用的范围。并且,在您的示例中,基础范围是 a ref_view,它本身总是借用(按照始终借用的相同原则R&)。

\n

请注意这里:

\n
auto begin() { return std::ranges::begin(values()); }\nauto end() { return std::ranges::end(values()); }\n
Run Code Online (Sandbox Code Playgroud)\n

ranges::begin仅当范围被借用时,传递右值范围才有效。那是[range.access.begin]/2.1

\n
\n

如果E是右值并且enable_\xc2\xadborrowed_\xc2\xadrange<remove_\xc2\xadcv_\xc2\xadt<T>>is false,ranges\xe2\x80\x8b::\xe2\x80\x8bbegin(E)则格式错误。

\n
\n

因此,因为您的原始代码已编译,所以您可以确定它是有效的。

\n

  • 看来P2017是在N4861之后才应用的。它仍然应该适用于 C++20 吗? (2认同)