Search code examples
c++option-typestdoptional

Why is there no built-in way to get a pointer from an std::optional?


Things like the following happen all to often when using std::optional:

void bar(const Foo*);

void baz(const std::optional<Foo>& foo) {
    // This is clunky to do every time.
    bar(foo.has_value() ? &foo.value() : nullptr);
    
    // Why can't I do this instead?
    bar(foo.as_ptr());
}

This is the sort of thing that makes it annoying to adopt std::optional, because then it's extremely inconvenient to use it with existing code that expects pointers instead. So why isn't something like .as_ptr() provided with std::optional? It seems like a pretty obvious convenience function.


Solution

  • To be pedantically correct, you need to use one of these instead:

    // `Foo` might have an overloaded `operator&`
    foo.has_value() ? std::addressof(foo.value()) : nullptr
    // You can shorten it to
    foo ? std::addressof(*foo) : nullptr
    // Or you can use `operator->()`
    foo ? foo.operator->() : nullptr
    // Which in C++20 can be accessed with a more readable utility function
    foo ? std::to_address(foo) : nullptr
    

    As for the "why", let's look at the proposal to add std::optional to the standard library, "A proposal to add a utility class to represent optional objects (Revision 5)".

    It's based on Boost.Optional, which does provide foo.get_ptr() with the exact semantics that you want.

    In fact, the original proposal has get_pointer(foo), but it was removed in the second revision. The change is described as "Removed duplicate interface for accessing the value stored by the optional.", as it was removed along with std::get(std::optional<T>&).

    You can simply use boost::optional instead, but it is not too hard to reimplement it yourself:

    // If you want it as a member
    namespace my {
    
    template<typename T>
    struct optional : std::optional<T> {
        using std::optional<T>::optional;
        T* get_ptr() noexcept { return has_value() ? std::addressof(value()) : nullptr; }
        const T* get_ptr() const noexcept { return has_value() ? std::addressof(value()) : nullptr; }
    };
    
    }
    
    // Or as a free function
    template<typename T>
    T* get_ptr(std::optional<T>& opt) noexcept {
        return opt.has_value() ? std::addressof(opt.value()) : nullptr;
    }
    
    template<typename T>
    const T* get_ptr(const std::optional<T>& opt) noexcept {
        return opt.has_value() ? std::addressof(opt.value()) : nullptr;
    }
    

    Or using the C++23 monadic operations on optionals, this can be foo.and_then([](auto& x) { return std::addressof(x); }).