range-based for-loop in C++ over std::optional<Container> does not work

range-based for-loop in C++ over std::optional<Container> does not work
typescript
Ethan Jackson

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>).

ChatGPT insisted that both loops in the code work. I tried to search stackoverflow, etc, and couldn't find the right answer to the particular problem I have.

Answer

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.

Related Articles