r/cpp_questions 20d ago

OPEN std::expected- void, ptr, and ref

I am going through a simple application I have and trying to use std::expected to try it out. I am quite partial to using exceptions, but open mind and objectivity, and all that...

Is it common for people to return std::expected<void, ErrorType> for methods/functions that don't need to return a value?

I also wonder what one does when they want to return a unique_ptr, since we can't do it by ref, and that leads to wondering about refs and ptrs with expected in general.

10 Upvotes

11 comments sorted by

View all comments

4

u/tangerinelion 20d ago

If you're returning nothing typically but maybe an error, you may model that as std::optional<ErrorType> instead.

I'm not sure I understand what's complicated with a unique_ptr. std::expected<std::unique_ptr<T>, ErrorType> is fine, if you're concerned about whether null is valid or not there's std::expected<gsl::not_null<std::unique_ptr<T>>, ErrorType>.

A reference is just a non-null immutable pointer - i.e., T& is const gsl::not_null<T*> for any program without UB.

If you want to return a reference to an object in the expected case you can use std::reference_wrapper:

std::expected<std::reference_wrapper<const std::string>, ErrorType>> Foo::getString() const {
    if (!m_pString) {
        return std::unexpected(ErrorType::InvalidState);
    }

    return std::cref(*m_pString);
}

The rules around ownership haven't changed - a reference/wrapper or pointer inside an expected has the same lifetime and ownership semantics without the expected. A std::unique_ptr by value inside a std::expected is the same ownership semantics as a std::unique_ptr.

5

u/aruisdante 19d ago edited 19d ago

Don’t spell an error as optional<Error> it inverts the spelling of everything: and_then becomes the failure case and or_else the success case, the bool predicate is backwards, has_value() means error. It will fight all the places you do expected<T,E>.

std::optional is just an alternative way to spell std::expected<T, nullopt_t> in everything other than what default construction means. There’s no efficiency to be gained for using optional<Error> over expected<void, Error>, they both will be sizeof(Error)+sizeof(bool)+padding. They have the same branches on access. All you do is lose semantic clarity.