C++ Logo

std-proposals

Advanced search

Re: Function proposal: generic value_or

From: Barry Revzin <barry.revzin_at_[hidden]>
Date: Wed, 18 Aug 2021 11:39:56 -0500
On Wed, Aug 18, 2021 at 10:36 AM Francesco Pretto via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Wed, 18 Aug 2021 at 14:01, Bo Persson via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
> > The fact that we already have some pretty useless operators is a very
> > weak argument for adding more special operators.
> >
>
> I think you misunderstood my words in the sense I consider at least
> the safe navigation operator very valuable, and several language
> designers clearly think the same because the list of languages
> implementing it is growing. The off-topic question was if it was
> formally proposed for C++ as well.
>

The problem with safe navigation in C++ is that we're not object-oriented
enough to have nil and we're not functional enough to have language
optional, so it seems like it would be difficult to specify.

For pointers:

struct C {
    int mem;
    int val();
    int& ref();
    int&& rref();
};
C* p = /* whatever */;
p?.mem; // this is the straightforward happy case, would be an int*
p?.val(); // this seems not possible?
p?.ref(); // this could be an int*?
p?.rref(); // this could be an int* but that's a bit weird and loses
information?

This is an even harder question for class types, because how do you get
this to work

optional<C> o = /* whatever */;
o?.mem;

operator->() kind of works this way now but we generally side-step the
question of "what is actually the member" by returning a pointer. But here,
we can't just return a C* since we need to know what the type of mem is so
we can provide the correct return type. But also the compiler needs to know
that it's looking in C for these members. o?.val() is totally reasonable
here and could return optional<int>, how do we do that?

At least in the optional case, this sort of thing does come up, and the
current solution is to use map (or transform, in C++23), but in these
simple cases it's... pretty verbose:

o?.mem;
o.map([](C& c) { return c.mem; })

But also o?.mem should probably give you an Optional<int&>, so in order to
really mean the same thing, the fair comparison is:

o?.mem;
o.map([](C& c) -> decltype(auto) { return (c.mem); })

And that's not just long but also very complicated. This only gets worse
when you want to invoke a member function with some parameters.

I think the closest thing I've seen that would make this work (and I think
it would actually completely make this work, simply by using the same idea
for a different operator) is P0060, which would work here as

template <typename T>
class Optional {
    template <typename F, typename... Args>
    auto operator?.(F&& f, Args&&... args) {
        using U = std::invoke_result_t<F, T&, Args...>;
        return (*this)
            ? Optional<U>(std::in_place, std::invoke(FWD(f), **this,
FWD(args)...))
            : Optional<U>();
    }
};

That is, o?.mem evaluates as o.operator?.(/* synthetic unary function that
returns .name on its operand */) and o?.foo(1, 2) evaluates as
o.operator?.(/* synthetic n-ary function that returns .foo(args...) on its
operands */, 1, 2).

That paper never got a follow-up though.

Barry

Received on 2021-08-18 11:40:16