5

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

7
  • @3CxEZiVlQ Not sure the dupe is appropriate. The OP's range based for loop is not extending the lifetime based on the behavior they observe Commented Apr 28 at 15:36
  • 2
    "ChatGPT insisted that both loops in the code work" - LLMs/"AI" don't know the first thing about writing correct code. Don't waste your time on those tools. Commented Apr 28 at 15:36
  • 1
    duplicate is not applicable, since OP's code is valid in C++23 due to P2644. Commented Apr 28 at 15:39
  • 1
    @康桓瑋 but the question is tagged for C++17 and 20 Commented Apr 28 at 15:40
  • The extra part is that value() gives you a reference to the object contained within the optional, so the thing that needs lifetime extension isn't available to be extended Commented Apr 28 at 15:42

1 Answer 1

16

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.