P2266’s interaction with decltype(auto)
decltype(auto)
C++Now 2021 is happening this week. Normally it’s in Aspen, Colorado, but this year it’s completely online. I presented two talks:

“When Should You Give Two Things the Same Name?,” based on this blog post of mine from October 2020

“The Complete Guide to
return x
,” based on my committee papers P1155 “More Implicit Move” (adopted into C++20) and P2266 “Simpler Implicit Move” (proposed for C++23).
In the latter talk, my slide 77 showed a table illustrating how P2266’s valuecategory changes affected five subtly different functions. One of the entries was incorrect; and, as I fixed it, I realized that for completeness the table should have included eight subtly different functions!
Here is a revised and I hope fully correct version of that table. This revised version appears in the slide deck I submitted after the talk.
C++14/17/20  P2266 (C++23?)  

auto a(T x) > decltype(x) { return x; } 
T ^{1} 
T ^{1} 
auto b(T x) > decltype((x)) { return (x); } 
T& 
T& 
auto c(T x) > decltype(auto) { return x; } 
T ^{1} 
T ^{1} 
auto d(T x) > decltype(auto) { return (x); } 
T& 
T&& 
auto e(T&& x) > decltype(x) { return x; } 
illformed ^{2}  T&& 
auto f(T&& x) > decltype((x)) { return (x); } 
T& 
illformed ^{3} 
auto g(T&& x) > decltype(auto) { return x; } 
illformed ^{2}  T&& 
auto h(T&& x) > decltype(auto) { return (x); } 
T& 
T&& ^{4} 
1) Implicit move: the returned T
object is moveconstructed from x
.
2) The return type is deduced as T&&
, but lvalue x
cannot be returned as T&&
without a cast.
(C++20’s change to permit implicitmoving from rvalue reference variables is irrelevant.
Implicit move is not considered, because this is not a copyinitialization context.)
3) The return type is T&
; but xvalue x
cannot be returned as T&
without a cast.
4) In C++20, the return type is deduced as T&
because the returned expression (x)
is an lvalue.
After P2266, the return type is deduced as T&&
because the returned expression (x)
is an xvalue.
Observe that in C++20, functions f
and h
both do “rvalue laundering,”
accepting an rvalue reference and returning an lvalue reference, with no
visible cast. After P2266,
neither of them works anymore to launder rvalues: f
becomes illformed,
and h
becomes the identity function.
And just for total completeness:
C++14/17/20  P2266 (C++23?)  

auto t(T& x) > decltype(x) { return x; } 
T& 
T& 
auto u(T& x) > decltype((x)) { return (x); } 
T& 
T& 
auto v(T& x) > decltype(auto) { return x; } 
T& 
T& 
auto w(T& x) > decltype(auto) { return (x); } 
T& 
T& 