Hi!

As far as I remember boost::optional<T&> has rather surprising assignment semantics when assigned from T& (rebind for empty optional and assign-through-reference for non-empty one). And it's the best it can achieve if it is modeled after optional<T>. That's why it was not standardized. Maybe we just need another type - optional_reference<T>?  It can have different behavior more suitable for reference type.

Regards,
Gregory

чт, 29 июл. 2021 г. в 03:31, Barry Revzin via Std-Proposals <std-proposals@lists.isocpp.org>:


On Wed, Jul 28, 2021 at 2:40 PM Arthur O'Dwyer via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Wed, Jul 28, 2021 at 3:20 PM Edward Catmur via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Wed, 28 Jul 2021 at 20:16, Kyle Knoepfel via Std-Proposals <std-proposals@lists.isocpp.org> wrote:

Right...which is why I said the return type of value_for(...) would not be quite like std::optional.  So there seem to be four or five options to me:

1. std::map<K, V>::value_for(K const&) -> typename std::map<K, V>::value_handle, where map gains a nested type that behaves like std::optional<V&> would behave.  Yuck.
2. std::map<K, V>::value_for(K const&) -> V*, where a bare pointer, or some equivalent, is returned.  Also yuck.
3. std::map<K, V>::value_for(K const&) -> std::optional<V&>.  Requires convincing the standards committee to support reference-type template arguments for std::optional.
4. Something else I haven't thought of
5. Stop wanting this feature

I think 3 is the ideal solution, but the most unlikely to occur.  Option 1 is doable but I assume no one wants to add an additional type to std::map.  I fear 5 is the most likely solution, although it would sadden me.

4. optional<reference_wrapper<V>>? 


optional<reference_wrapper<T>> is a very poor substitute for optional<T&> in basically all respects.
 
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:
 
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>())) { ... }

And this isn't a problem at all - we're just composing different APIs together.

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?

With optional, these things just compose straightforwardly, so: m.value_at(p).transform(ranges::size).value_or(0)

How do you do that with a pointer? 

Note that neither of these examples work with optional<reference_wrapper<T>> either.
 
Which goes to illustrate what I think is my point: helper methods are super useful but also have way too many knobs to be worth standardizing. Just like type-erased callable wrappers, every codebase is going to want to make their own tradeoffs and control their own code.)

my $.02,
Arthur

The nice thing about optional is that it's optional that has the knobs, you don't need to reinvent all of those APIs.

Barry
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals