, 11 min read
Care is needed to use C++ std::optional with non-trivial objects
16 thoughts on “Care is needed to use C++ std::optional with non-trivial objects”
, 11 min read
16 thoughts on “Care is needed to use C++ std::optional with non-trivial objects”
Most of this seems quite expected, just like what you get with std::tuple or any other abstraction embedding one or several values of type T.
The value_or() issue is avoided by making default construction cheap, which is a good idea in general anyway, and easily achievable for purely “data” classes (but may not be possible if embedding something like a mutex).
I agree with your sentiment.
Rule of thumb (for me) is pointer types are nullable and therefore inherently ‘optional’ – thus the need for something that specifically isn’t a pointer type.
I actually forgot this today, so your blurb is quite timely for me.
The std::optional template is exactly as efficient or inefficient as any other value type. It works well with objects implementing move-only semantics.
It works well with objects implementing move-only semantics.
Here is a code sample with such a class:
smart pointers can efficiently accommodate the optional pattern for everything other than primitives and rvalues.
This creates an issue when templating code because neither style is optimal in all cases.
Minor nitpick, I think you meant to pass z to f(). Otherwise, good stuff!
The 5th case will not make a copy in most use cases (e.g., A a = g(); is guranteed to *not* make a copy). And other comments are right too — there’s nothing special about std::optional. You will get the similar behaviour with just std::string or any other value type.
Several times I have already wished for an emplacing value_or(), i.e., one that accepts ctor arguments and constructs the alternative iff the optional is empty.
Of course one can write that oneself as a free function, but it would be nice to have it in the std::lib.
I don’t think this has much to do with optional but more to do with pass by value vs reference. I think most of the superfluous copies could have been avoided but passing const T& vs T.
is there anything specific to optional here… you’d have the same behavior with any type
Thanks for the code and thoughts.
The line
return f(a);
probably should pass ‘z’ instead of ‘a’.
I’m going to assume that the last example should include
f(z)
In Rust it is actually really clear what happens. It’s a move. No copies involved:
fn f(z: Option<A>) -> A {
// the use of the lambda avoids constructing the default unless z is None
return z.unwrap_or_else(|| A::default());
}
fn g() -> A {
let a = A::new(“message”.into());
// move a
let z = Some(a);
// move z
return f(z);
}
fn main() {
let _ = g();
}
// no copy / clone
struct A {
c: String,
}
impl Default for A {
fn default() -> A {
return A {
c: “default”.into()
};
}
}
impl A {
fn new(s: String) -> A {
A {
c: s
}
}
}
Useful article. Would be interesting to see the assembly output if you put on godbolt, with optimization. Then can see how many copies are really made.
Scary how the potentials of Artificial Intelligence can shape our lives
The way the function returns the value is actually very clever f(z).
nice job thank you