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

2

7

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.

Trehalose answered 22/9, 2022 at 15:13 Comment(19)
&foo.value() ? foo.has_value() : nullptr is not just "clunky" it is wrong. If foo does not contain a value then value() throws an exception.Godiva
did you perpahs swap has_value and value in the code?Godiva
You can still create a free function helper to do that... Resulting to bar(as_ptr(foo));.Stegosaur
Yes, whoops, my bad, been using too much Python lately lol. Will edit.Trehalose
If you had that as_ptr(), you would need to check its result for nullptr at the place where this result was used. I don't see any benefit over checking whether an optional has some value.Lightman
There's nothing stopping you from writing T const* as_ptr(std::optional<T> const&).Regnant
@DanielLangr But bar() already does that. If I could re-write bar() and all other functions like it to use std::optional instead of a pointer, then I wouldn't need to do this.Trehalose
what would annoy me much more is that bar takes its parameter by pointer. You can't blame std::optional for that :PGodiva
@DanielLangr: it is to inter-operate with "old" code (or C code, or ...) which uses (nullble) pointers.Stegosaur
@Regnant Nothing stopping me, but something so simple and frequent shouldn't require a roll-your-own helper function that I would then have to put in some util library and include it almost everywhere I use optionals.Trehalose
It reminds my question about missing std::construct_at. A few years later, it got into the standard (not because of my question ;). But note that anyone can prepare a proposal for extending C++, including the addition of your desired function.Lightman
i suppose it has something to do with operator-> being undefined when the optional contains no value. If it could return a nullptr without incurring additional cost it would be the method you are looking for.Godiva
@463035818_is_not_a_number I don't think this explains anything. The -> operator has nothing to do with this. The as_ptr function that I would like to see would be an exact stand-in for the code I have above.Trehalose
@Matt: "something so simple and frequent" Something that is "simple and frequent" for you may not be "simple and frequent" for other people.Monolayer
"has nothing to do with this" ? Sorry I dont get that. It is the method that returns a pointer to the contained value. If it would return a nullptr in case there is no value, it would be a replacement for foo.has_value() ? &foo.value() : nullptrGodiva
@NicolBolas The simplicity is not relative. It is simple. It can be implemented as already shown above. Frequency is indeed relative, but I really do think that anyone who isn't writing new code from scratch but wants to adopt the std::optional paradigm would run into this situation.Trehalose
@463035818_is_not_a_number no it is not the "method that returns a pointer to the contained value". It is is the deference and call operator. You can't call a non-existent value anyway, that's why it has undefined semantics if there is no value. The way you get the pointer is as I have shown above. If you use the -> operator to get the pointer, then you are really doing something wrong.Trehalose
@matt You can call operator-> explicitly to get a pointer. But, as 463 points out, the result is undefined if the optional doesn't contain a value, which is a bit of a shame.Kev
@PaulSanders that's not what it's meant for. You can get a pointer as I have already demonstrated with &foo.value() or &*foo, but none of these options would safely return nullptr when the optional is empty.Trehalose
B
11

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

Badtempered answered 22/9, 2022 at 16:9 Comment(1)
The pitfall of std::addressof usage give a point to provide it in std IMO :-)Stegosaur
E
3

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);
}
Edelweiss answered 22/9, 2022 at 15:25 Comment(4)
I don't see any problem here. Yes, std::optional<Foo*>::as_ptr() would then return Foo**, which is exactly what older code would expect for an optional-pointer-to-Foo. I also believe that templated code is smart enough these days to disable a function in cases where it actually doesn't make sense. If it makes any difference, as_ptr can be named something more descriptive to make its semantics clearer in the general case.Trehalose
why not just as_ptr(f)?Webfooted
You don't need to specify the template argument when invoking as_ptr, since the argument T is deducable from the type of optional passed in: std::cout << as_ptr(f);Yser
for C++20 I would preffer auto* as_ptr(std::optional<auto>& in) It's more explicit and restrictive.Webfooted

© 2022 - 2024 — McMap. All rights reserved.