Search code examples
c++pointersreferencec++17auto

const auto reference binding to (null) pointer - what is the actual type?


While looking into some code, I came across a construct with the following line:

if (const auto& foo = std::get_if<MyType>(&bar)) // note the ampersand!

where bar is a std::variant<MyType, OtherType>. The problem here is that get_if may return a null pointer and I don't understand why the statement works.

Consider this similar MCVE:

#include <iostream>

struct Foo { int a = 42; };

Foo* f() { return nullptr; }

int main() {
    const auto& foo = f();          // Returns a nullptr that binds to Foo*& - UB?
    //static_assert(std::is_same<decltype(foo), const Foo*&>::value); // -> Fails
    //const Foo*& bar = f(); // -> Fails

    if (foo)    std::cout << foo->a << std::endl;
    else        std::cout << "nullpointer" << std::endl;
}

The first line of main() works fine, and I would expect the type of barto be const Foo*&, but the static assertion fails. Unsurprisingly, the following line also fails to compile with cannot bind non-const lvalue reference of type 'const Foo*&' to an rvalue of type 'const Foo*'.

What happens in the first statement of main? Is this UB or does the standard contain some hidden secret that allows this to be legal? What is the type of bar?


Solution

  • Note that for const auto& foo, const is qualified on the auto part, i.e. the pointer but not the pointee. Then the type of foo would be Foo* const &, which is a reference to const (pointer to non-const Foo), but not const Foo* &, which is a reference to non-const (pointer to const Foo).

    And the lvalue-reference to const could bind to rvalue returned by f(), so const auto& foo = f(); works fine; const Foo*& bar = f(); won't work because bar is an lvalue-reference to non-const; which can't bind to rvalue. Changing the type of bar to const Foo * const & or Foo* const & (same as foo) would make it work.