I'm trying to iterate over a std::vector<X> contained in a struct T that I access through a std::optional<T>. Contrary to my expectations, the behavior is different if I first store the std::optional<T> into a copy versus if I iterate it directly:
#include <optional>
#include <string>
#include <vector>
// The rest is the same in both implementations
struct Object
{
std::string id;
std::vector<SomeStructType> items;
};
Object readDataFile() { /*....*/ return {}; }
bool dataFileExists() { /*....*/ return true; }
std::optional<Object> getData()
{
if (!dataFileExists()) {
return std::nullopt;
}
Object obj = readDataFile();
return obj;
}
int main()
{
// Implementation 1 - works
auto items = getData().value().items;
for (auto const& item : items)
{
// will normally iterate over items
}
// Implementation 2 - for loop is skipped
for (auto const& item : getData().value().items)
{
// this body will never execute
}
}
I would expect those two to behave the same. Why does implementation 2 skip the for loop? `
The file read is the same in both cases, with items.size() == 1. I omitted some other nullopt checks and asserts in the provided code, but I don't expect them to be relevant.
In your second implementation
getData()returns a temporarystd::optional<Object>which will be destroyed end of the expression (in other words, even before the range-based for loop starts).The range-based for loop thus holds the ranges of the invalid
items. Using this leads to your program's undefined behavior. In your case, the loop didn't get executed.That being said, if the compiler warnings are turned on, you would have already been warned about it. For instance, clang 15 with
-Wall -Wextra -pedantic-errors -fsanitize=address,undefinedsays:You can increase the lifetime with some tweaks. See an example mentioned in this post.