Daniel Lemire's blog

, 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”

  1. Antoine says:

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

  2. Malachi says:

    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.

  3. Oren Tirosh says:

    The std::optional template is exactly as efficient or inefficient as any other value type. It works well with objects implementing move-only semantics.

    1. It works well with objects implementing move-only semantics.

      Here is a code sample with such a class:

      #include <optional>
      

      struct A { A() = default; A(const A&) = delete; A(A&&) = default;

      };

      A f() { A a; std::optional<A> z(std::move(a)); return std::move(z.value()); }

  4. B K says:

    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.

  5. Minor nitpick, I think you meant to pass z to f(). Otherwise, good stuff!

  6. John says:

    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.

  7. Matthäus Brandl says:

    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.

  8. Bruce Visscher says:

    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.

  9. uh says:

    is there anything specific to optional here… you’d have the same behavior with any type

  10. Stefan says:

    Thanks for the code and thoughts.

    The line
    return f(a);
    probably should pass ‘z’ instead of ‘a’.

  11. Kristof says:

    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>) -&gt; A {
    // the use of the lambda avoids constructing the default unless z is None
    return z.unwrap_or_else(|| A::default());
    }

    fn g() -&gt; 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() -&gt; A {
    return A {
                c: “default”.into()
    };
    }
    }

    impl A {
    fn new(s: String) -&gt; A {
    A {
                c: s
    }
    }
    }

  12. Jonny Grant says:

    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.

  13. Brian says:

    Scary how the potentials of Artificial Intelligence can shape our lives

  14. Nelson says:

    The way the function returns the value is actually very clever f(z).

  15. rozmusic says:

    nice job thank you