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

Time:09-23

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.

CodePudding user response:

I don't have an official answer, but to me it's simply not generic enough to make sense in all cases. std::optional<std::shared_ptr<Foo>> is perfectly valid (though we may question why someone takes this approach), as is std::optional<Foo*>.

So what would as_ptr() do in each of the above cases? std::shared_ptr<Foo>*? Foo**? Those are the correct types per your request, but likely won't result in just drop in replacements in your code.

You could write a simple template for your use case if you like:

template <class T>
T* as_ptr(std::optional<T>& in)
{
    return in.has_value() ? &in.value() : nullptr;
}

// note C  20 allows this (more terse) signature
// auto* as_ptr(auto& in);

int main()
{
    std::optional<int> f;

    std::cout << as_ptr<int>(f);
}

CodePudding user response:

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

  • Related