1. Introduction
Functions can be overloaded to accept different types of arguments,
including Lvalues and Rvalues. However, it is currently
not possible to specify a function that accepts only a PRvalue.
A function with a parameter type such as
can take both an XRvalue and a PRvalue, however this proposal aims to
introduce new syntax,
, to indicate that a function can only
accept a PRvalue as an argument.
2. Motivation
2.1. optional < T >:: emplace
Consider the
class in the C++ standard library, which
provides a way to represent optional values. It includes the
method that constructs the contained object in-place.
With the proposed feature, we can extend the functionality of
to have an overload which only accepts a PRvalue argument, which would allow us to emplace an
unmovable-and-uncopyable type (such as
). This allows for
more flexibility and efficiency in code, as illustrated in the following
example:
extern int some_global_variable ; extern std :: mutex FuncReturnsMutexByValue ( int , int , int ); int main ( void ) { optional < std :: mutex > om ; om . emplace ( FuncReturnsMutexByValue ( 1 , 2 , some_global_variable ) ); }
In this example,
is invoked with a PRvalue argument
returned from the function
. The compiler
determines the appropriate overload of
based on
the argument type, allowing seamless integration of PRvalue parameters.
Note that the arguments passed to
,
i.e.
, are evaluated before the invocation
of the
method, however the actual invocation of
is delayed until explicitly requested inside
the implementation of the
method.
3. Possible Implementation
The implementation of the proposed feature involves changes to the C++
core language to recognise and handle the
syntax. It also requires
updates to the standard library to support functions and methods that
accept PRvalue parameters.
3.1. optional < T >:: emplace
template < typename T > T & optional < T >:: emplace ( T &&& arg ) { this -> reset (); T & retval = *:: new ( this -> buffer ) T ( arg () ); this -> has_value_bool = true; return retval ; }
In the above implementation, you can see that the argument,
,
behaves as though it were a functor object. The invocation of
results in the invocation of
. Note however
that the arguments passed to
,
i.e.
, were evaluated before the
invocation of the
method.
3.2. x86_64 Linux, Apple, FreeBSD
Let’s give consideration to the following program which uses the new feature proposed in this paper:
#include <cstdint>// uint64_t #include <atomic>// atomic<T> #include <new>// "placement new" using std :: atomic , std :: uint64_t ; atomic < uint64_t > FuncReturnsAtomicByValue ( uint64_t const a , uint64_t const b ) { return a + b + 0x7f ; } template < typename T > class MyOptional { alignas ( T ) char unsigned buf [ sizeof ( T )]; bool has_value_bool = false; public : template < typename Param > T & emplace ( Param &&& arg ) { T * const p = static_cast < T *> ( static_cast < void *> ( buf )); if ( this -> has_value_bool ) { this -> has_value_bool = false; p ->~ T (); } :: new ( p ) T ( arg () ); this -> has_value_bool = true; return * p ; } }; uint64_t GiveMeFive ( void ) { return 5u ; } int main ( void ) { MyOptional < std :: atomic < uint64_t > > var ; var . emplace ( FuncReturnsAtomicByValue ( 4u , GiveMeFive ()) ); }
Let’s focus on the following line inside
:
var . emplace ( FuncReturnsAtomicByValue ( 4u , GiveMeFive ()) );
And now let’s write the x86_64 assembler for this line. Note that on
x86_64 for Linux, Apple and FreeBSD, the first three function
arguments go in the registers RDI, RSI, RCX, and
the return value goes in RAX. The first thing that happens is that
we evaluate all of the arguments to
and we
push them onto the stack right-to-left:
call GiveMeFive push rax ; push return value from GiveMeFive onto stack push 4 ; push 4 onto stack
The compiler must output a separate helper function which invokes
after moving the arguments from the stack
into the registers. Note that the address of the return slot
is in RDI.
Helper_Function : mov rax , rsi ; address of arguments in memory mov rsi , [ rax + 0 ] ; move 4 from memory to register mov rcx , [ rax + 8 ] ; move 5 from memory to register jmp FuncReturnsAtomicByValue
The method
accepts a PRvalue parameter, which is
implemented as a function which takes three parameters:
-
RDI = The
pointer for thethis
objectstd :: optional -
RSI = Address of Helper_Function mentioned above
-
RCX = Address of arguments to pass to
FuncReturnsAtomicByValue
Really it’s as though the
method had been written:
T & std :: optional < T >:: emplace ( T ( * helper_func )( void * ), void * args ) { T * const p = static_cast < T *> ( static_cast < void *> ( buf )); if ( this -> has_value_bool ) { this -> has_value_bool = false; p ->~ T (); } :: new ( p ) T ( helper_func ( args ) ); this -> has_value_bool = true; return * p ; }
And so then the C++ code inside
becomes the following assembler:
call GiveMeFive push rax ; push return value from GiveMeFive onto stack push 4 ; push 4 onto stack mov rdi , var ; set 1 st argument to address of var mov rsi , Helper_Function ; set 2 nd argument to address of Helper_Function mov rcx , rsp ; set 3 rd argument to current stack pointer call emplace
4. Design considerations
Introducing PRvalue parameters into the language may have implications for existing codebases. While the addition of this feature does not render any existing code ill-formed, it may affect how functions and methods are called and overloaded. Careful consideration should be given to backward compatibility and potential conflicts with existing code that relies heavily on function parameter matching.
Additionally, the introduction of PRvalue parameters may require adjustments to coding conventions and best practices to ensure consistent and efficient usage across codebases. Documentation and educational materials should be updated to reflect the new syntax and its implications on function parameter passing.
5. Revision history
R0 => R1
-
Syntax changed from
to^^
.&&& -
Wording of value categories corrected (i.e. XRvalue, PRvalue).
-
Implementation in x86_64 assembler for Linux, Apple, FreeBSD.
6. Acknowledgements
For their feedback and contributions on the mailing list std-proposals@lists.isocpp.org:
Gašper Ažman, Thiago Macieira, Matthew Taylor