C++ Logo

std-proposals

Advanced search

Re: Yet another member function for std::map

From: Григорий Шуренков <grigorij1981_at_[hidden]>
Date: Thu, 29 Jul 2021 16:19:08 +0300
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_at_[hidden]>:

>
>
> On Wed, Jul 28, 2021 at 2:40 PM Arthur O'Dwyer via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Wed, Jul 28, 2021 at 3:20 PM Edward Catmur via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> On Wed, 28 Jul 2021 at 20:16, Kyle Knoepfel via Std-Proposals <
>>> std-proposals_at_[hidden]> 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:
>>
>> https://github.com/zenorogue/hyperrogue/pull/246/files#diff-f42f1abd2e52db6ab3f7da5cc6c2e290adc46e1d7cd47c673ea49f5ead738c96R851
>>
>> 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_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2021-07-29 08:19:21