Note: A small change is required to the core language.
1. Introduction
While it is currently possible to return an unmovable-and-uncopiable class by value from a function:
std :: counting_semaphore < 8 > FuncReturnsByValue ( unsigned const a , unsigned const b ) { return std :: counting_semaphore < 8 > ( a + b ); }
It is not possible to emplace this return value into an
:
int main () { std :: optional < std :: counting_semaphore < 8 > > var ; var . emplace ( FuncReturnsByValue ( 1 , 2 ) ); // compiler error }
This paper proposes a solution to this debacle, involving an addition to the standard library, along with a small change to the core language.
2. Motivation
2.1. emplace ( FuncReturnsByValue () )
There is a workaround to make this possible, and it is to use a helper class with a conversion operator:
int main () { std :: optional < std :: counting_semaphore < 8 > > var ; struct Helper { operator std :: counting_semaphore < 8 > () { return FuncReturnsByValue ( 1 , 2 ); } }; var . emplace ( Helper () ); }
This is possible because of how the
member function is written:
template < typename ... Params > T & emplace ( Params && ... args ) { . . . :: new ( buffer ) T ( forward < Params > ( args )... ); . . . }
The compiler cannot find a constructor for
which accepts a sole argument of type
, and so it invokes the conversion operator, meaning we effectively have:
:: new ( buffer ) T ( FuncReturnsByValue ( 1 , 2 ) );
In this situation, where we have a prvalue returned from a function, we have guaranteed elision of a copy/move operation.
This proposal aims to simplify this technique by adding a new function to the standard library called
which can be used as follows:
int main () { std :: optional < std :: counting_semaphore < 8 > > var ; var . emplace ( std :: elide ( FuncReturnsByValue , 1 , 2 ) ); }
3. Possible implementation
namespace std { template < typename R , typename F_ref , typename ... Params_refs > requires is_same_v < R , remove_reference_t < R > > // To ensure F returns by value class elide_t final { using F = remove_reference_t < F_ref > ; F && f ; // 'f' is always an Rvalue reference tuple < Params_refs ... > const args_tuple ; // just a tuple full of references explicit elide_t ( F && arg , Params_refs ... args ) noexcept : f ( move ( arg ) ), args_tuple ( static_cast < Params_refs > ( args )... ) {} public : operator R ( void ) noexcept ( noexcept ( apply ( static_cast < F_ref > ( f ), move ( args_tuple )))) { return apply ( static_cast < F_ref > ( f ), move ( args_tuple ) ); } template < typename F2 , typename ... Params2 > friend auto elide ( F2 && , Params2 && ... ) noexcept ; }; template < typename F , typename ... Params > auto elide ( F && f , Params && ... args ) noexcept { return elide_t < decltype ( invoke ( forward < F > ( f ), forward < Params > ( args )... ) ), conditional_t < is_lvalue_reference_v < F > , F & , F && > , conditional_t < is_lvalue_reference_v < Params > , Params & , Params && > ... > ( move ( f ), forward < Params > ( args )... ); } }
4. Design considerations
4.1. template constructor
The above implementation of
will not work in a situation where a class has a constructor which accepts a specialisation of the template class
as its sole argument, such as the following
:
class AwkwardClass { std :: mutex m ; // cannot move, cannot copy public : template < typename T > AwkwardClass ( T && arg ) { cout << "In constructor for AwkwardClass, \n " "type of T = " << typeid ( T ). name () << endl ; } }; AwkwardClass ReturnAwkwardClass ( int const arg ) { return AwkwardClass ( arg ); } int main ( int const argc , char ** const argv ) { std :: optional < AwkwardClass > var ; var . emplace ( std :: elide ( ReturnAwkwardClass , -1 ) ); }
This program will print out:
-- In constructor for AwkwardClass, type of T = std::elide_t< AwkwardClass, AwkwardClass (&)(int), int&& > --
The problem here is that the constructor of
has been instantiated with the template parameter type
set to a specialisation of
, when really we wanted
to be set to
. We want the following output:
-- In constructor for AwkwardClass, type of T = int --
A workaround here is to apply a constraint to the constructor of
as follows:
template < typename T > requires ( ! is_specialization_v < std :: remove_cvref_t < T > , std :: elide_t > ) AwkwardClass ( T && arg ) { cout << "In constructor for AwkwardClass, \n " "type of T = " << typeid ( T ). name () << endl ; }
In order that class definitions do not have to be altered in order to apply this constraint to template constructors, this proposal makes a change to the core language to prevent the constructor of any class type from having a specialisation of
as any of its parameter types.
5. Proposed wording
The proposed wording is relative to [N4950].
In subclause 13.10.3.1.11 [temp.deduct.general], append a paragraph:
11 -- Attempting to instantiate a constructor in which a parameter has a type of a specialization of std::elide_t.
6. Impact on the standard
This proposal is a small library extension combined with a small change to the core language. The change to the core language is one short paragraph to be added to 13.10.3.1.11 [temp.deduct.general]. The text addition is 17 words, and the addition has no effect on any other part of the standard.
7. Impact on existing code
No existing code becomes ill-formed. The behaviour of all existing code is unaffected by this addition to the standard library.