Note: A change is required to the core language.
1. Introduction
While it is currently possible to return an unmovable-and-uncopyable 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 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 class 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
#include <functional>// invoke #include <tuple>// apply, tuple #include <type_traits>// invoke_result, is_same, remove_reference, true_type #include <utility>// move namespace std { template < typename F_ref , typename ... Params_refs > class elide { public : typedef std :: true_type tag_elide ; private : using R = invoke_result_t < F_ref , Params_refs ... > ; static_assert ( is_same_v < R , remove_reference_t < R > > ); // F must return by value 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 public : template < typename F , typename ... Params > explicit elide ( F && arg , Params && ... args ) noexcept // see explicit deduction guide : f ( move ( arg )), args_tuple ( static_cast < Params &&> ( args )... ) {} operator R ( void ) noexcept ( noexcept ( apply ( static_cast < F_ref > ( f ), move ( args_tuple )))) { return apply ( static_cast < F_ref > ( f ), move ( args_tuple ) ); } /* -------- Delete all miranda methods -------- */ elide ( void ) = delete ; elide ( elide const & ) = delete ; elide ( elide && ) = delete ; elide & operator = ( elide const & ) = delete ; elide & operator = ( elide && ) = delete ; elide const volatile * operator & ( void ) const volatile = delete ; template < typename U > void operator ,( U && ) = delete ; /* -------------------------------------------- */ }; template < typename F , typename ... Params > elide ( F && , Params && ...) -> elide < F && , Params && ... > ; // explicit deduction guide template < typename T > concept has_tag_elide_true = T :: tag_elide :: value ; }
Thoroughly tested on GodBolt: https://godbolt.org/z/rd5qbfE7E
You can comment out Line #85 in the godbolt to see the effect of the core language change.
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< 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 ( ! ( ( 1u == sizeof ...( Params )) && ( is_elider < Params > || ...) )) 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
from having a specialisation of
as its sole parameter.
This constraint is achieved by detecting the presence of a
called
for which
evaluates to true
.
The programmer can write their own custom class to use instead of
,
and in order to take advantage of the core language feature which prevents
the instantiation of constructors, the programmer must give their custom
class an accessible
as follows:
typedef std :: true_type tag_elide ;
Furthermore, if the programmer so wishes, they can derive their own
class from
and change the
in order to disable
the failure of template instantiation:
template < typename F_ref , typename ... Params_refs > class MyElide : public std :: elide < F_ref , Params_refs ... > { public : typedef std :: false_type tag_elide ; . . . };
5. Proposed wording
The proposed wording is relative to [N4950].
In subclause 13.10.3.1.11 [temp.deduct.general], append a paragraph under the heading "Type deduction can fail for the following reasons:"
11 -- Attempting to instantiate a constructor that has exactly one parameter, and the sole parameter is a type which contains an accessible typedef for a type whose 'value' evaluates to true, for example: typedef std::true_type tag_elide;
6. Impact on the standard
This proposal is a library extension combined with a change to the core language. The change to the core language is a paragraph to be added to 13.10.3.1.11 [temp.deduct.general]. 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.
8. Revision history
R0 => R1
-
The class
is not defined asstd :: elide final -
Template instantiation only fails if the constructor has exactly one parameter.
-
Template instantiation fails based upon the presence of a
tag whosetypedef
evalutes tovalue true
.
9. Acknowledgements
For their feedback and contributions on the mailing list std-proposals@lists.isocpp.org:
Jens Mauer, Ville Voutilainen, Jonathan Wakely
And for their insightful blogs:
Andrzej Krzemieński, Arthur O’Dwyer