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
#include <cstddef>// size_t #include <functional>// invoke #include <tuple>// get, tuple, tuple_element_t, apply #include <type_traits>// conditional, is_lvalue_reference, is_same, remove_cvref_t, remove_reference #include <utility>// index_sequence, make_index_sequence, move, forward namespace std { // The following function 'apply_perfectly' is required // because 'apply' has a flaw. The flaw is that 'apply' // cannot be used with a tuple that contains an Rvalue // reference. The 'static_cast' ensures that the Rvalue // references are not passed as Lvalues. template < typename F , typename Tuple , size_t ... I > constexpr decltype ( auto ) apply_perfectly_impl ( F && f , Tuple && t , index_sequence < I ... > ) { return invoke ( forward < F > ( f ), static_cast < tuple_element_t < I , remove_cvref_t < Tuple > > > ( get < I > ( forward < Tuple > ( t ) ) )... ); } template < typename F , typename Tuple > constexpr decltype ( auto ) apply_perfectly ( F && f , Tuple && t ) { return apply_perfectly_impl ( forward < F > ( f ), forward < Tuple > ( t ), make_index_sequence < tuple_size_v < remove_cvref_t < Tuple > > > {}); } 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 ; 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 ) { return apply_perfectly ( static_cast < F_ref > ( f ), 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 the 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 ) noexcept { std :: cout << typeid ( T ). name () << std :: endl ; } }; AwkwardClass ReturnAwkwardClass ( int const arg ) { return AwkwardClass ( arg ); } int main () { std :: optional < AwkwardClass > var ; var . emplace ( std :: elide ( ReturnAwkwardClass , -1 ) ); }
The problem here is that this will result in the invocation of the constructor of
with the template parameter type
set to a specialisation of
. A workaround here is to apply a constraint to the constructor of
as follows:
template < typename T > requires ( ! std :: is_specialization_v < std :: remove_cvref_t < T > , std :: elide_t > ) AwkwardClass ( T && arg ) noexcept { std :: cout << typeid ( T ). name () << std :: 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 11.4.5 [class.ctor], append a paragraph:
9 A constructor shall not have any parameter type which is a specialization of std::elide_t. A template constructor, after the substitution of type parameters, shall not have any parameter type which is 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 Section 11.4.5 [class.ctor]. The text addition is 35 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 exisiting code is unaffected by this addition to the standard library.