On Wed, Jul 28, 2021 at 8:29 PM Barry Revzin <barry.revzin@gmail.com> wrote:
On Wed, Jul 28, 2021 at 2:40 PM Arthur O'Dwyer via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
 
2, "return bare pointer," is correct.

optional<T&> is a much better choice. Semantically, it matches the functionality better (you want either a T& or nothing, which is exactly the semantics of optional<T&>, while T* is more ambiguous) and the API of optional<T&> is just much better for this problem than the API of T*, in terms of ergonomics for the user. As is very clear from your example:

Meh, I would argue that what you want is either the address of a T or something-which-is-falsey-and-also-not-a-T, which is exactly what `T*` gives you (because null pointers).
Historically this user interface has been so abundantly successful that when C++17 adopted `std::optional`, they pasted the whole "pointer" interface onto it, so that e.g. you can write `o->foo()` instead of `o.value().foo()`.

The STL already uses this idiom in `std::get_if` and `std::any_cast`, as well as `std::dynamic_cast<T*>`.
Actually, many industry codebases already have a convenience function along these lines. Serendipitously, I just added one to HyperRogue the other day:

However, notice that there are many operations that you'd still want to do via third-party convenience functions. For example, in the exact same HyperRogue patch, I also introduced a helper function `span_at`, with these semantics:

template<class Map, class Key, class T = /*metaprogramming*/>
span<const T> span_at(const Map& map, const Key& key) {
    auto it = map.find(key);
    return (it == map.end()) ? span<const T>() : span<const T>(it->second.data(), it->second.size());
}

This is useful for things like
    const std::map<Parent, std::vector<Child>> m = ...;
    Parent p = ...;
    for (const Child& c :  span_at(m, p)) { ... }
where a naïve
    for (const Child& c : m[p])
would try to insert into `m`.
(And yes there's prior art for this API in optional::value_or. But `value_or(m, p, span<T>())` would have been much too verbose, and would have had trouble with type-checking, so for this codebase we want the shorter simpler version.

If you had a member function value_at that returned an optional, this would be:

for (child Child& c : m.value_at(p).value_or(span<T>())) { ... }

Sure, that's the option I presented in the previous sentence and said
- it would have been much too verbose: compare
 for (const Child& c : hr::span_at(m, p)) { ... }
 for (const Child& c : m.value_at(p).value_or(std::span<Child>())) { ... }
- it would have had trouble with type-checking: `vector` is convertible to `span`, but is not literally the same as `span`, so .value_or won't compile it
 https://godbolt.org/z/fMvb1Gs3a

What would you do if value_at returned a T*? You can't do value_or() on a T*, because pointers have very few operations you can do on them (and nearly all of those would be straight up invalid in this use-case - which does not make for a great API!) you'd either push for some language feature that does that or you'd write some non-member function that handles this case. Which would be fine for value_or(), but not for any number of other operations that work for optional but not for pointers. What if you wanted the size of the span there, or 0?

Right — it sounds like you're agreeing with my point. There are just too many things you could imagine doing with a helper function here.
 for (const Child& c : hr::size_at(m, p)) { ... }
or
 for (const Child& c : m.value_at(p).transform(std::ranges::size).value_or(0)) { ... }
(FWIW, this requires grafting monadic `transform` onto `std::optional` — but that's https://github.com/cplusplus/papers/issues/112 , likely to happen in C++23 anyway)
And then what if you wanted "the integer at that key position, if it exists, but only if it's even"? You could graft monadic `filter` onto `optional`... or, again, you could write a helper function. There will always be functions like this. You can write them yourself as free functions, or you can graft them onto `map`; or you can graft them onto `optional`; or you could even invent a new sort of `monadic_return<T>` that you can graft arbitrary functions onto, without choosing `optional` specifically to be that type. But (1) the stream of functions and knobs is never-ending, and (2) I'd rather write `size_at` than `value_at().transform(ranges::size).value_or(0)` any day.

–Arthur