让我先从一段C++
代码开始,它简化了我在实际代码库中遇到的问题。我用--std=c++20
和编译了它--std=c++17
。下面的第一个 for 循环没有问题;而第二个 for 循环(它返回结果)std::optional<Container>
在我尝试过的所有多个容器中都不行。我想了解原因:
#include <iostream>
#include <optional>
#include <string>
#include <unordered_set>
std::unordered_set<std::string> GenerateSet() {
std::unordered_set<std::string> names = {"a", "b"};
return names;
}
std::optional<std::unordered_set<std::string>> GenerateOptionalSet() {
std::unordered_set<std::string> names = {"a", "b"};
return names;
}
int main() {
std::cout << "When a set is returned: {";
for (const auto& e : GenerateSet()) {
std::cout << e << " ";
}
std::cout << "}" << std::endl;
std::cout << "When a optional of a set is returned: {";
for (const auto& e : GenerateOptionalSet().value()) {
std::cout << e << " ";
}
std::cout << "}" << std::endl;
return 0;
}
结果是运行时出现分段错误(相当新clang
),或者在第二个 for 循环中根本没有迭代(gcc
在古老的 Linux 机器上相当旧)。
std::optional<T>::value()
这是我提到的有关:
std::optional::value()的 URL,来自 cppreference.com
似乎有 4 个不同的版本。我不太确定这 4 个重写函数中哪个版本会被调用,以及为什么它没有按我预期的方式工作(即只是循环遍历返回的临时值std::optional<T>
)。
这里的问题是你的引用绑定到了什么。在 C++20 中,右边的引用
:
绑定到一个auto&&
变量,所以对于第一个循环,你有这是可以的,因为
GenerateSet()
返回一个右值std::unordered_set<std::string>
并range_ref
延长返回的右值的生命周期。通过第二次循环,你得到
这是一个右值,它调用一个函数,该函数由于
value()
通过引用返回而产生一个左值。因此,不存在临时的生命周期扩展,并且你的引用现在是一个悬垂引用。任何对该引用的访问都具有未定义的行为,并且你得到的任何结果都是正确的。这已经在 C++23 中使用P2644来解决,它将延长中间右值对象的生命周期。