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 bar
to 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
?
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.