Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a safe navigation operator for C++?

Tags:

c++

c++17

In Modern C++, is there a way to do safe navigation?

For example, instead of doing...

if (p && p->q && p->q->r)
    p->q->r->DoSomething();

...having a succinct syntax by using some sort of short-circuiting smart pointer, or some other kind of syntax leveraging operator overloading, or something in the Standard C++ Library, or in Boost.

p?->q?->r?->DoSomething(); // C++ pseudo-code.

Context is C++17 in particular.

like image 746
Eljay Avatar asked Jul 17 '17 16:07

Eljay


People also ask

What is safe navigation operator in C#?

C# 6.0 includes a new null-conditional operator (sometimes called the Safe Navigation Operator): In previous versions null checking for any kind of objects manually we need to write if condition then we need to write our required properties or else we need to write our business logic.

What does safe navigation operator do?

Use the safe navigation operator ( ?. ) to replace explicit, sequential checks for null references. This operator short-circuits expressions that attempt to operate on a null value and returns null instead of throwing a NullPointerException.

What does safe navigation operator return?

In object-oriented programming, the safe navigation operator (also known as optional chaining operator, safe call operator, null-conditional operator, null-propagation operator) is a binary operator that returns null if its first argument is null; otherwise it performs a dereferencing operation as specified by the ...

What is safe navigation operator in rails?

Safe navigation operator¶ ↑ &. , called “safe navigation operator”, allows to skip method call when receiver is nil . It returns nil and doesn't evaluate method's arguments if the call is skipped.


2 Answers

The best you can do is collapse all the member accesses into one function. This assumes without checking that everything is a pointer:

template <class C, class PM, class... PMs>
auto access(C* c, PM pm, PMs... pms) {
    if constexpr(sizeof...(pms) == 0) {
        return c ? std::invoke(pm, c) : nullptr;
    } else {
        return c ? access(std::invoke(pm, c), pms...) : nullptr;
    }
}

Which lets you write:

if (auto r = access(p, &P::q, &Q::r); r) {
    r->doSomething();
}

That's ok. Alternatively, you could go a little wild with operator overloading and produce something like:

template <class T>
struct wrap {
    wrap(T* t) : t(t) { }
    T* t;

    template <class PM>
    auto operator->*(PM pm) {
        return ::wrap{t ? std::invoke(pm, t) : nullptr};
    }

    explicit operator bool() const { return t; }
    T* operator->() { return t; }
};

which lets you write:

if (auto r = wrap{p}->*&P::q->*&Q::r; r) {
    r->doSomething();
}

That's also ok. There's unfortunately no ->? or .? like operator, so we kind of have to work around the edges.

like image 173
Barry Avatar answered Nov 15 '22 12:11

Barry


"With a little Boilerplate..."

We can get to this:

p >> q >> r >> doSomething();

Here's the boilerplate...

#include <iostream>

struct R {
    void doSomething()
    {
        std::cout << "something\n";
    }
};

struct Q {
    R* r;
};

struct P {
    Q* q;
};

struct get_r {};
constexpr auto r = get_r{};

struct get_q {};
constexpr auto q = get_q{};

struct do_something {
    constexpr auto operator()() const {
        return *this;
    }
};
constexpr auto doSomething = do_something {};

auto operator >> (P* p, get_q) -> Q* {
    if (p) return p->q;
    else return nullptr;
}

auto operator >> (Q* q, get_r) -> R* {
    if (q) return q->r;
    else return nullptr;
}

auto operator >> (R* r, do_something)
{
    if (r) r->doSomething();
}

void foo(P* p)
{
//if (p && p->q && p->q->r)
//    p->q->r->DoSomething();
    p >> q >> r >> doSomething();
}

The resulting assembly is very acceptable. The journey to this point may not be...

foo(P*):
        test    rdi, rdi
        je      .L21
        mov     rax, QWORD PTR [rdi]
        test    rax, rax
        je      .L21
        cmp     QWORD PTR [rax], 0
        je      .L21
        mov     edx, 10
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        jmp     std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
.L21:
        ret
like image 39
Richard Hodges Avatar answered Nov 15 '22 12:11

Richard Hodges