Reputation: 131385
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.
Upvotes: 8
Views: 1657
Reputation: 17436
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).
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});
}
Upvotes: 11
Reputation: 1988
Here's a variation that works in c++17 (if you delete the concepts) and supports chaining. I opted for return by value. One could incorporate perfect forwarding if performance is a concern.
#include <cassert>
#include <optional>
#include <concepts>
template <typename T>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename T>
inline constexpr bool is_optional_v = is_optional<T>::value;
template <typename T>
concept IsOptional = is_optional_v<T>;
template<typename T, class O>
IsOptional auto operator |(const std::optional<T>& obj, O const T::*member)
{
if constexpr (is_optional_v<O>) {
return obj ? (*obj).*member : std::nullopt;
}
else {
return obj ? std::optional<O>{(*obj).*member} : std::nullopt;
}
}
template<typename T, class O>
IsOptional auto operator |(const T& obj, O const T::*member)
{
if constexpr (is_optional_v<O>) {
return obj.*member;
}
else {
return std::optional<O>{obj.*member};
}
}
int main()
{
using Bar = int;
struct Foo {
Bar x;
};
Foo foo{123};
std::optional<Foo> foo2{Foo{123}};
std::optional<Foo> foo3{};
assert((foo | &Foo::x) == 123);
assert((foo2 | &Foo::x) == 123);
assert((foo3 | &Foo::x) == std::nullopt);
}
void chain()
{
struct B {
int x;
};
struct A {
std::optional<B> b;
};
std::optional<A> val1;
std::optional<A> val2 = A{};
std::optional<A> val3 = A{B{123}};
A val4 = A{B{123}};
auto x1 = val1 | &A::b | &B::x;
assert(!x1);
auto x2 = val2 | &A::b | &B::x;
assert(!x2);
auto x3 = val3 | &A::b | &B::x;
assert(*x3 == 123);
auto x4 = (val4 | &A::b) | &B::x;
assert(*x4 == 123);
}
Upvotes: 2
Reputation: 5321
(C++23) With a little operator|
overloading to do piping, and taking a page out of the monad playbook, could do something like...
#include <iostream>
#include <optional>
#include <type_traits>
#include <utility>
using Bar = int;
struct Foo { Bar x; };
struct FFoo { std::optional<Bar> x{}; };
inline auto f(std::optional<Foo> obj) -> std::optional<Bar> {
return obj ? obj->x : std::nullopt;
}
inline auto ff(std::optional<FFoo> obj) -> std::optional<Bar> {
return obj ? obj->x : std::nullopt;
}
inline void println(std::optional<Bar> obj) {
if (!obj) {
std::cout << "(empty)\n";
} else {
std::cout << *obj << "\n";
}
}
// C++23 magic to use operator| to pipe calls.
template <typename T, typename FN> requires (std::invocable<FN, T>)
constexpr auto operator|(T&& t, FN&& f) -> typename std::invoke_result_t<FN, T> {
return std::invoke(std::forward<FN>(f), std::forward<T>(t));
}
int main() {
std::optional<FFoo> no_foo;
std::optional<FFoo> foo_no_bar{ Foo{} };
std::optional<FFoo> foo_bar{ Foo{ 10 } };
std::optional<Foo> original_foo{ Foo{ 20 } };
std::optional<Foo> original_no_foo{};
no_foo | ff | println;
foo_no_bar | ff | println;
foo_bar | ff | println;
original_foo | f | println;
original_no_foo | f | println;
}
Update: renamed my modifed Foo
to FFoo
, f
to ff
, added the original Foo
and original f
back in, and added original_foo
and original_no_foo
objects to demonstrate use.
Upvotes: 1
Reputation: 4183
Depending on use case you can use either of 4 monadic functions:
optional::value_or
. This method expects a default value.std::optional<Foo> optfoo;
Foo foo = foo.value_or(Foo{});
nullopt
with optional::transform
:std::optional<Foo> optfoo;
std::optional<Bar> optbar = foo.transform(&Foo::x);
This method can change the result type from optional<foo>
to optional<bar>
.
optional::and_then
.std::optional<Foo> optfoo;
std::optional<Bar> optbar = foo.and_then([](Foo& foo){
return std::optional{foo.x};
});
The difference from transform
is that the lambda returns an optional
.
optional::or_else
:std::optional<Foo> optfoo;
std::optional<Foo> optfoo2 = foo.or_else([]{
return std::optional{Foo{}};
});
This method keeps the type of optional<foo>
, but tries to replace the value(like value_or
).
Option 1 is the only C++17 solution and the only one that doesn't take a lambda/function as its input, and the only one that doesn't necessarily return an optional
.Other options need C++23, return optional values and need function inputs.
Upvotes: 1
Reputation: 131385
A hacky, but arguably more fun, tweak of Mooing Duck's answer would let us use:
map(obj, x)
instead of Javascript's obj?.x
.
Yes, it's this simple because macros are involved >:-)
#include <optional>
#include <type_traits>
using Bar = int;
struct Foo { Bar x; };
template<class I, class O>
std::optional<O> map_impl(const std::optional<I>& val, O const I::*member)
{ return val ? std::optional<O>{*val.*member} : std::nullopt; }
#define map(obj_, field_) map_impl(obj_, &std::remove_reference_t<decltype(*obj_)> :: field_ )
Notes:
qdot
.obj qdot x
.Upvotes: 3
Reputation: 66912
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++.
Upvotes: 5