Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ equivalent of Javascript's '?.' operator for optional-chaining?

In Javascript, if I have a potentially null object obj, which, if not null, will have a field x - I can write obj?.x. This is called "optional chaining" or "safe navigation": If obj is null, or otherwise not an object with accessible fields - it won't throw, just produce null. (It also won't throw if obj is an object but doesn't have an x field, although that's less relevant for this question).

Now let us turn to C++, which since C++17 has standard-library std::optional<T>'s. Suppose we have:

struct Foo { Bar x; }

and I have an optional<Foo> object named obj. Naively, I might write the expression:

obj ? obj->x : optional<Bar>{}

... but this won't work, since the types of the two result expression is different, and the trinary operator doesn't harmonize them. Godbolt confirms, with this code:

#include <optional>

using Bar = int;
struct Foo { Bar x; };

std::optional<Bar> f()
{
    std::optional<Foo> obj {};
    return obj ? obj->x : std::nullopt;
}

So, what short idiom is there for expressing obj?.x in C++?


Note: Any language standard version is fine, but the older the better obviously.

like image 719
einpoklum Avatar asked Jun 06 '26 09:06

einpoklum


2 Answers

C++23 introduced Monadic operations for optional, so you can use std::optional::transform

#include <optional>

using Bar = int;
struct Foo { Bar x; };

std::optional<Bar> f()
{
    std::optional<Foo> obj {};
    return obj.transform([](auto&& o) { return o.x;});
}

for lower C++ versions you could use C++11 optional library which has a similar operation (map), or write your own version of transform (not recommended).

godbolt demo

I don't recommend you write you own transform because to make it chainable you need to inherit from std::optional, and it is likely UB to inherit from the standard library objects.


A shorter version would define the lambda as a struct externally, if you are using it everywhere and need to reduce the number of letters per use. (with the exact same assembly)

template <typename Member>
struct grab
{
    Member member;
    auto operator()(auto& obj) const 
    { return obj.*member; }
};

std::optional<Bar> f()
{
    std::optional<Foo> obj {};
    return obj.transform(grab{&Foo::x});
}
like image 125
Ahmed AEK Avatar answered Jun 09 '26 02:06

Ahmed AEK


I was surprised to discover C++ option lacked a map method, but after a few minutes thought, it seems easy to emulate.

template<class I, class O>
std::optional<O> map(const std::optional<I>& val, O const I::*member)
{ return val ? std::optional<O>{*val.*member} : std::nullopt; }

template<class I, class O, class...Args>
std::optional<O> map(const std::optional<I>& val, O (I::*method)() const, Args&&...args)
{ return val ? std::optional<O>{(*val.*method)(std::forward<Args>(args)...)} : std::nullopt; }

And then usage is terse:

map(obj, &Foo::x);
map(obj, &Foo::getX); //if you need to pass parameters to this, you can

This only works on members and methods, and not arbitrary function calls, but arbitrary functinon calls would require lambdas, which are annoyingly verbose in C++.

like image 43
Mooing Duck Avatar answered Jun 09 '26 01:06

Mooing Duck



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!