You can do something like this (pseudocode-ish; link to buildable code is provided below):
// wrap std::optional for chaining
template <class T> class Maybe {
std::optional<T> t;
// ... constructors etc
// Maybe chaining
// If A has a member named m of type M,
// then Maybe<A>.fetch(&A::m) returns a Maybe<M>
template <class M>
Maybe<M> fetch(M T::*mem_ptr) {
return (bool(t)) ? Maybe<M>((*t).*mem_ptr) : Maybe<M>() ;
}
// Maybe chaining special case
// If A has a member named m, which is itself a Maybe<M>,
// then return it without wrapping it in an additional Maybe
template <class M>
Maybe<M> fetch(Maybe<M> T::*mem_ptr) {
return (bool(t)) ? ((*t).*mem_ptr) : Maybe<M>() ;
}
};
Now if you have this:
struct C { int d ; }
struct B { C c; }
struct A { B b; }
A a;
Maybe<A> ma;
and you can do this
int d = a.b.c.d;
you cannot do the same with ma, but you can use the next best thing, namely:
Maybe<int> md = ma.fetch(&A::b).fetch(&B::c).fetch(&C::d);
And you can still use this if you Maybe-ify any or all struct members above:
struct C { Maybe<int> d ; }
struct B { Maybe<C> c; }
struct A { Maybe<B> b; }
Live example (not production quality but it builds).
Answer from n. m. could be an AI on Stack OverflowYou can do something like this (pseudocode-ish; link to buildable code is provided below):
// wrap std::optional for chaining
template <class T> class Maybe {
std::optional<T> t;
// ... constructors etc
// Maybe chaining
// If A has a member named m of type M,
// then Maybe<A>.fetch(&A::m) returns a Maybe<M>
template <class M>
Maybe<M> fetch(M T::*mem_ptr) {
return (bool(t)) ? Maybe<M>((*t).*mem_ptr) : Maybe<M>() ;
}
// Maybe chaining special case
// If A has a member named m, which is itself a Maybe<M>,
// then return it without wrapping it in an additional Maybe
template <class M>
Maybe<M> fetch(Maybe<M> T::*mem_ptr) {
return (bool(t)) ? ((*t).*mem_ptr) : Maybe<M>() ;
}
};
Now if you have this:
struct C { int d ; }
struct B { C c; }
struct A { B b; }
A a;
Maybe<A> ma;
and you can do this
int d = a.b.c.d;
you cannot do the same with ma, but you can use the next best thing, namely:
Maybe<int> md = ma.fetch(&A::b).fetch(&B::c).fetch(&C::d);
And you can still use this if you Maybe-ify any or all struct members above:
struct C { Maybe<int> d ; }
struct B { Maybe<C> c; }
struct A { Maybe<B> b; }
Live example (not production quality but it builds).
C++23 introduces and_then or or_else to address this inconvenience.
Here is some paper with proposal.
Before we can use C++23 you can try write some template which could resolve this.
My attempt:
namespace detail {
template <auto Field, class T>
struct field_from_opt;
template<typename T, typename FieldType, FieldType T::*ptr>
struct field_from_opt<ptr, T>
{
static auto get(const std::optional<T>& x) -> std::optional<FieldType>
{
if (x) return (*x).*ptr;
return {};
}
};
}
template<auto Field, typename T>
auto if_exists(const std::optional<T>& x)
{
return detail::field_from_opt<Field, T>::get(x);
}
https://godbolt.org/z/dscjYqrx1
Videos
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});
}
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++.
Jumping into a new code base and it seems like optional chaining is used EVERYWHERE.
data?.recipes?.items
label?.title?.toUpperCase();
etc.
It almost seems like any time there is chaining on an object, it always includes the ?.
Would you consider this an anti-pattern? Do you see any issues with this or concerns?
I agree that these tags should be synonymised. There is also a null-propagation-operator tag which should be included in this.
I think safe-navigation-operator shouldn't be the canonical one. It has the fewest tagged questions, and it doesn't include null in its name. I also think "navigation" could be misleading to people who aren't already familiar with this usage of it (normally I would say property access, array access, etc., not property navigation or array navigation). I would favour null-conditional-operator or null-propagation-operator being the canonical one, since the word null appearing in the tag name makes the tag more useful when it appears on the main page or in searches.
I am admittedly biased as I encounter the null-conditional-operator on nearly a daily basis, and if I had a question about it, that's the only term I'd recognize of the three proposed.
I'll repeat a comment by Heretic Monkey to the OP here:
"optional chaining" in JavaScript is slightly different from "null conditional operator" in C# due to the presence of undefined in JavaScript. Optional chaining takes into account both null and undefined, whereas C#'s null conditional operator does not have to. I don't know if that's enough of a difference to keep them separate (there may be questions unique to JS's handling of undefined), but I thought I'd throw it out there.
To me, this is sufficient differentiation not to combine at these two tags. They are specifically documented features of major programming languages, and have differences in their implementations.
I won't speak to the more vague "safe navigation" operator and wouldn't oppose combining it with something or making it angular-specific, but I see more value in the separate tags than I would with a combined/synonomized tag.
You need to put a . after the ? to use optional chaining:
myArray.filter(x => x.testKey === myTestKey)?.[0]
Playground link
Using just the ? alone makes the compiler think you're trying to use the conditional operator (and then it throws an error since it doesn't see a : later)
Optional chaining isn't just a TypeScript thing - it is a finished proposal in plain JavaScript too.
It can be used with bracket notation like above, but it can also be used with dot notation property access:
const obj = {
prop2: {
nested2: 'val2'
}
};
console.log(
obj.prop1?.nested1,
obj.prop2?.nested2
);
Run code snippetEdit code snippet Hide Results Copy to answer Expand
And with function calls:
const obj = {
fn2: () => console.log('fn2 running')
};
obj.fn1?.();
obj.fn2?.();
Run code snippetEdit code snippet Hide Results Copy to answer Expand
Just found it after a little searching on the what's new page on official documentation
The right way to do it with array is to add . after ?
so it'll be like
myArray.filter(x => x.testKey === myTestKey)?.[0] // in case of object
x?.() // in case of function
I'll like to throw some more light on what exactly happens with my above question case.
myArray.filter(x => x.testKey === myTestKey)?[0]
Transpiles to
const result = myArray.filter(x => x.testKey === myTestKey) ? [0] : ;
Due to which it throws the error since there's something missing after : and you probably don't want your code to be transpilled to this.
Thanks to Certain Performance's answer I learned new things about typescript especially the tool https://www.typescriptlang.org/play/index.html .