Let me start with a C++
code that simplifies my issues I faced in the actual code base. I compiled it with --std=c++20
and --std=c++17
. The first for-loop below was okay; the second for-loop, which returns std::optional<Container>
was not, for all of the multiple containers I have tried. I'd like to understand why:
#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;
}
The result was segmentation faults at runtime (fairly recent clang
) or no iteration at all in the second for-loop (fairly old gcc
on an archaic Linux box).
Here's the URL I referred to regarding the std::optional<T>::value()
:
std::optional::value() from cppreference.com
There seem to be 4 different versions. I was not quite sure which version of the 4 overridden functions would be invoked and why it does not work as I expected (i.e. just looping over the value of the returned, temporary std::optional<T>
).
The issue here is what your reference gets bound to. In C++20 the right hand side of the :
gets bound to an auto&&
variable so for the first loop you have
auto&& range_ref = GenerateSet();
and this is okay since GenerateSet()
returns an rvalue std::unordered_set<std::string>
and range_ref
extends the lifetime of the returned rvalue.
With your second loop you get
auto&& range_ref = GenerateOptionalSet().value();
which is an rvalue that calls a function that yields an lvalue since value()
returns by reference. Because of this there is no temporary lifetime extension and your reference is now a dangling reference. Any access of the reference has undefined behavior and any results you get are correct.
This has been addresses in C++23 with P2644 which will extend the lifetime of the intermediate rvalue object.