Date: Mon, 26 Jan 2026 15:07:35 +0000
One of the main features of "return if" is that it preserves the value
category of the expression.
If the expression is the invocation of a function, the return type
could be T (i.e. PRvalue), or T&& (i.e. XRvalue), or T& (i.e. Lvalue).
Without "return if", we have to create a temporary variable, and that
means we are now going for NRVO instead RVO -- but NRVO isn't mandated
by the Standard, which means we lose the ability to return an
uncopyable-and-immovable object.
But what if we want to check the validity of an immovable object
before returning it? Like the following:
unsigned counter = 0u;
struct Mutex {
long long n;
Mutex(void) : n(0) {}
Mutex(Mutex const & ) = delete;
Mutex(Mutex &&) = delete;
operator bool(void) { return ++counter & 1u; }
};
Mutex Callee(void)
{
return Mutex();
}
Mutex EnclosingFunction(void)
{
/* The following lambda preserves the value category
Lvalue becomes Lvalue
XRvalue becomes XRvalue
PRvalue becomes XRvalue (not a typo) */
constexpr auto preserve =
[](auto &&expr) constexpr noexcept -> decltype(auto)
{
return static_cast<decltype(expr)&&>(expr);
};
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
}
int main(void)
{
Mutex m = EnclosingFunction();
}
The above code fails to compile because of the following line:
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
The return statement tries to either copy or move an
uncopyable-and-immovable class.
This problem can be remedied by rewriting EnclosingFunction as follows:
Mutex EnclosingFunction(void)
{
return if Callee();
return if Callee();
return if Callee();
return if Callee();
return if Callee();
}
Now I know what you're thinking . . . how can we possibly have 5
objects constructed in the one return slot? Well let's take a look at
what would happen on x86_64 SystemV:
The return slot is passed in the RDI register, so basically it's:
void EnclosingFunction( void *return_slot );
The first "return if" constructs an object in the return slot, checks
if it's true, but it's false, so it destroys the object (freeing up
the return slot).
Then the second, third, fourth and fifth "return if" do the same
thing, re-using the return slot each time.
I realise this needs a bit more thinking and work but I'm doing bits
and bobs on it in between taking breaks from chimeric_ptr.
category of the expression.
If the expression is the invocation of a function, the return type
could be T (i.e. PRvalue), or T&& (i.e. XRvalue), or T& (i.e. Lvalue).
Without "return if", we have to create a temporary variable, and that
means we are now going for NRVO instead RVO -- but NRVO isn't mandated
by the Standard, which means we lose the ability to return an
uncopyable-and-immovable object.
But what if we want to check the validity of an immovable object
before returning it? Like the following:
unsigned counter = 0u;
struct Mutex {
long long n;
Mutex(void) : n(0) {}
Mutex(Mutex const & ) = delete;
Mutex(Mutex &&) = delete;
operator bool(void) { return ++counter & 1u; }
};
Mutex Callee(void)
{
return Mutex();
}
Mutex EnclosingFunction(void)
{
/* The following lambda preserves the value category
Lvalue becomes Lvalue
XRvalue becomes XRvalue
PRvalue becomes XRvalue (not a typo) */
constexpr auto preserve =
[](auto &&expr) constexpr noexcept -> decltype(auto)
{
return static_cast<decltype(expr)&&>(expr);
};
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
}
int main(void)
{
Mutex m = EnclosingFunction();
}
The above code fails to compile because of the following line:
if ( decltype(auto) x = Callee(); preserve(x) ) return preserve(x);
The return statement tries to either copy or move an
uncopyable-and-immovable class.
This problem can be remedied by rewriting EnclosingFunction as follows:
Mutex EnclosingFunction(void)
{
return if Callee();
return if Callee();
return if Callee();
return if Callee();
return if Callee();
}
Now I know what you're thinking . . . how can we possibly have 5
objects constructed in the one return slot? Well let's take a look at
what would happen on x86_64 SystemV:
The return slot is passed in the RDI register, so basically it's:
void EnclosingFunction( void *return_slot );
The first "return if" constructs an object in the return slot, checks
if it's true, but it's false, so it destroys the object (freeing up
the return slot).
Then the second, third, fourth and fifth "return if" do the same
thing, re-using the return slot each time.
I realise this needs a bit more thinking and work but I'm doing bits
and bobs on it in between taking breaks from chimeric_ptr.
Received on 2026-01-26 15:07:49
