C++17 introduced a new function signature for a few emplace_back()
type functions (std::optional<>
has one too), but they are not ref-qualified. This allows emplace_back()
to be called on a temporary and bound to lvalue references, even though the lifetime of the object is not extended. Consider the following:
#include <vector>
#include <optional>
#include <iostream>
struct A {
A(int i) : i(i) { std::cout << "constructor called: " << i << '\n'; }
~A() { std::cout << "destructor called\n"; }
int i;
};
int main() {
auto & a = std::optional<A>().emplace(5);
std::cout << "a: " << a.i << '\n';
auto & v = std::vector<A>().emplace_back(5);
std::cout << "v: " << v.i << '\n';
// This fails to compile, because value() *is*
// ref-qualified so it cannot bind to an lvalue reference
//auto & a2 = std::optional<A>(std::in_place, 5).value();
auto const & a2 = std::optional<A>(std::in_place, 5).value();
std::cout << "a2: " << a2.i << '\n';
}
The output:
constructor called: 5
destructor called
a: 5
constructor called: 5
destructor called
v: 0
constructor called: 5
destructor called
a2: 5
I couldn't find any existing bugs or questions related to this, but maybe I'm just missing something. In the case of std::optional<>::value()
, it mostly works, but still allows binding to const lvalue references whilst not properly extending the contained type's lifetime.
Is there any reason why these functions are not ref-qualified, and why std::optional<>::value()
doesn't properly extend the lifetime of the contained object when used on an rvalue?
Next to nothing in the std library is ref-qualified.
As for value
, it returns a rvalue reference. When you have an rvalue qualified method returning a reference to internal state, you have two choices.
You can return a value, or you can return an rvalue reference.
If you return a value, this means .value()
on an rvalue optional does a move, while .value()
on an lvalue optional does not. This could be surprising.
The cost of .value()
returning an rvalue is that reference lifetime extension does not apply. The cost of .value()
returning a copy on an rvalue is the cost of the move, plus the surprise that it behaves differently.
Both have downsides. I have a memory of it being a discussion point in the design of optional. If my memory is correct, that means the decision was made with open eyes.
Barring an extreme improvement to reference lifetime extension, functions on rvalues returning rvalues to internal state are always going to bind to const&
and not extend the lifetime of the external object.
The real pain point in my experience is for(:)
loops;
std::optional<std::vector<int>> try_get_vec();
for (int x : try_get_vec().value()) // I know the optional won't be empty
the above has a dangling reference in it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With