1. Motivation
1.1. Allowing user-defined tuples, or "true" motivation :)
The section addresses the LEWG feedback. While we always had in mind that with the facility we can make a generic concept for types, we assumed it to be developed as a separate
proposal. However, based on the LEWG poll (§ 8.2 Library Evolution Telecon 2024-01-23), the paper should enable
user-defined tuples to make its motivation strong enough.
So, let’s go to the problem statement. Today the C++ standard defines types as only 5 types
from the namespace:
-
std :: tuple -
std :: pair -
std :: array -
(since C++26)std :: complex -
std :: ranges :: subrange
That sounds like a huge limitation for a generic library because, in principle, user-defined types could be treated like
tuples. and are already customizable by users. The problematic part is which is not a customization point.
Furthermore, there is already partial support for user-defined tuples in the language. For example the structured binding language feature has special rules for finding a function for an arbitrary type (in a nutshell, either as a
non-static-member-function or by argument-dependent lookup).
Unfortunately, rules are different in different places in the standard today. For example, has-tuple-element exposition-only concept for allows only the 5 types listed above and does not
consider user-defined types.
[P2165R4] added constraints for existing APIs (like , , etc.) to take and also provides better compatibility between objects
by adding extra APIs, which is great. The unfortunate part of the story, however, is that the mentioned APIs are still
limited by the definition of the concept (the 5 standard types).
In this paper, we proposed a new customization point object, , which can be used to extend the tuple protocol
to user-defined types. Since this facility uses argument-dependent lookup for , using it to redefine should not be a breaking change for the vast majority of the code.
See the example in § 3.2 Breaking code example section for more details.
For the following (simplified) code snippet:
namespace user { template < typename T , typename U > struct my_tuple_like { public : my_tuple_like ( T tt , U uu ) : t ( tt ), u ( uu ) {} private : T t ; U u ; template < std :: size_t I > friend auto get ( my_tuple_like < T , U > t_like ) { static_assert ( I == 0 || I == 1 ); if constexpr ( I == 0 ) return t_like . t ; else if constexpr ( I == 1 ) return t_like . u ; } }; } // namespace user namespace std { template < typename T , typename U > struct tuple_size < user :: my_tuple_like < T , U >> : std :: integral_constant < std :: size_t , 2 > {}; template < typename T , typename U > struct tuple_element < 0 , user :: my_tuple_like < T , U >> { using type = T ; }; template < typename T , typename U > struct tuple_element < 1 , user :: my_tuple_like < T , U >> { using type = U ; }; } // namespace std
please see the Before-After table
| Before | After |
|---|---|
|
|
Of course, is just an example. would work with any API that supports types.
1.2. The original motivating use case
Having , and other types as the value types for algorithms
creates a plenty of opportunities. With special views, such as , we can
specify which tuple elements to access when iterating over collections of such objects. However,
we cannot easily use a predicate to make a decision based on only some of tuple elements, for example keys or values.
Let’s consider the following example:
std :: vector < std :: tuple < int , int >> v {{ 3 , 1 },{ 2 , 4 },{ 1 , 7 }}; std :: ranges :: sort ( v , []( auto x , auto y ) { // key-based sorting return std :: get < 0 > ( x ) < std :: get < 0 > ( y ); });
As we can see, users should spell some extra syntax out to achieve the necessary goal, comparing to what is described in § 1.2.2 The desired approach. The example above can be considered simplified; in real practice users might also need to think of e.g. adding references to lambda parameters to avoid copying.
The code above can be rewritten with structured binding:
std :: vector < std :: tuple < int , int >> v {{ 3 , 1 },{ 2 , 4 },{ 1 , 7 }}; std :: ranges :: sort ( v , []( auto x , auto y ) { // key-based sorting auto [ key1 , value1 ] = x ; auto [ key2 , value2 ] = y ; return key1 < key2 ; });
Though one could say that it makes code simpler or at least more readable, on the other hand, its syntax forces the programmer to give names to otherwise unneeded variables, which is often considered a bad practice.
With [P2169R3] the situation with unused variables for structured binding becomes better but still might require the user to write a quite amount of underscores depending on the use case:
std :: vector < std :: tuple < int , int , int , int >> v {{ 3 , 1 , 1 , 1 },{ 2 , 4 , 4 , 4 },{ 1 , 7 , 7 , 7 }}; std :: ranges :: sort ( v , []( auto x , auto y ) { // key-based sorting auto [ key1 , _ , _ , _ ] = x ; auto [ key2 , _ , _ , _ ] = y ; return key1 < key2 ; });
1.2.1. Projections-based alternative
Projections provide another option to achieve the same behavior:
std :: ranges :: sort ( v , std :: less {}, []( auto x ) { // key-based sorting return std :: get < 0 > ( x ); });
A variant that properly handles references would use a generic lambda:
[]( auto && x ) -> auto && { // key-based sorting return std :: get < 0 > ( std :: forward < decltype ( x ) > ( x )); }
While this code achieves the desired result, it requires more syntactic boilerplate (lambda, forwarding etc.) than the useful code.
1.2.2. The desired approach
The nicest way to get what we want would be:
// The code that does not work because std::get is not fully instantiated std :: ranges :: sort ( v , std :: less {}, std :: get < 0 > );
But it doesn’t work because is a function template, and one cannot pass function
templates as arguments without instantiating them.
1.2.3. Why not std :: ranges :: views :: elements
The necessary result cannot be achieved with , which
would apply the filter for all operations on the input data, including element swap
(for sort algorithm), while we need it to be only be applied for the comparator.
| std::ranges::views::elements | Desired behavior |
|---|---|
|
|
1.3. Radix sort use case
Counting-based sorts, and Radix Sort in particular, provide another motivating use case.
Today it is not possible to have a C++ standard conformant implementation that uses
Radix Sort algorithm underneath because the complexity of is defined as
the number of comparator calls, while counting-based sorts do not use a comparator at all.
However, the industry needs Radix Sort for performance reasons. Implementations of C++ standard
parallel algorithms, such as oneAPI Data Parallel C++ Library (oneDPL) and CUDA Thrust, use Radix Sort
conditionally under the hood of , checking data types of the input and the comparator.
In this case, a special comparator is of no help to sort values by keys, and projections seem the only viable option.
That makes the proposed API applicable wider than just with the C++ standard library use cases.
1.4. Other use cases
With the appearance in the standard the easy use of projection for objects might become even more important because its dereferenceable type is exactly .
Another example where it would be convenient to pass to a function we discovered in a blog article by Barry Revzin.
Let’s assume there is a function that returns over a type:
auto find ( Key const & ) const -> optional < pair < Key const , Value > const &> ;
Now imagine the caller wants to use only one element out of the returned object, or some
default value in case the received is empty. Applying directly to the result would require
providing default values for all tuple elements, so it makes sense to convert it first to a single-element optional
with , and only then get the value with :
find ( key ). transform ( get < 1 > ). value_or ( -1 );
except, quoting the author, "of course that you can’t just pass like that, so it’s not as easy at it should be."
2. Proposed API
We propose the following API:
inline namespace /* unspecified */ { template < size_t I > inline constexpr /* unspecified */ get_element = /* unspecified */ ; } inline constexpr auto get_key = get_element < 0 > ; inline constexpr auto get_value = get_element < 1 > ;
With that API the motivating use case code with the desired behavior would be:
std :: vector < std :: tuple < int , int >> v {{ 3 , 1 },{ 2 , 4 },{ 1 , 7 }}; std :: ranges :: sort ( v , std :: less {}, std :: get_element < 0 > );
or even
std :: vector < std :: tuple < int , int >> v {{ 3 , 1 },{ 2 , 4 },{ 1 , 7 }}; std :: ranges :: sort ( v , std :: less {}, std :: get_key );
Let’s look at comparison tables (a.k.a. Tony Tables):
Comparison of proposed API with comparator-based version
| Before | After |
|---|---|
|
|
Comparison of proposed API with projections-based version
| Before | After |
|---|---|
|
|
2.1. Possible implementation
namespace std { namespace __detail { template < std :: size_t _Ip > struct __get_element_fn { template < typename _TupleLike > requires requires { typename std :: tuple_size < std :: remove_cvref_t < _TupleLike >>:: type ; } auto operator ()( _TupleLike && __tuple_like ) const -> decltype ( auto ) { constexpr bool __get_member_well_formed = requires { std :: forward < _TupleLike > ( __tuple_like ). template get < _Ip > (); }; if constexpr ( __get_member_well_formed ) { return std :: forward < _TupleLike > ( __tuple_like ). template get < _Ip > (); } return get < _Ip > ( std :: forward < _TupleLike > ( __tuple_like )); } }; } // namespace __detail inline namespace __get_element_namespace { template < std :: size_t _Ip > inline constexpr __detail :: __get_element_fn < _Ip > get_element ; } // inline namespace __get_element_namespace inline constexpr auto get_key = get_element < 0 > ; inline constexpr auto get_value = get_element < 1 > ; } // namespace std
2.2. tuple - like concept
With the proposed CPO, the concept can be generalized to cover
wider range of types rather than just the listed standard types.
2.2.1. tuple - like concept generalization with get_element
With we can define an exposition only helper concept can-get-tuple-element in the following way:
// necessary to check if std::tuple_size_v is well-formed before using it template < typename T > concept has - tuple - size = // exposition only requires { typename std :: tuple_size < T >:: type ; }; template < class T , std :: size_t I > concept can - get - tuple - element = // exposition only has - tuple - size < std :: remove_cvref_t < T >> && requires ( T && t ) { typename std :: tuple_element_t < I , std :: remove_cvref_t < T >> ; { std :: get_element < I > ( std :: forward < T > ( t )) } -> std :: convertible_to < const std :: tuple_element_t < I , std :: remove_cvref_t < T >>&> ; };
Then the concept can use can-get-tuple-element and do something like:
template < typename T > concept tuple - like = // exposition only has - tuple - size < std :: remove_cvref_t < T >> && [] < std :: size_t ... I > ( std :: index_sequence < I ... > ) { return (... && can - get - tuple - element < T , I > ); } ( std :: make_index_sequence < std :: tuple_size_v < std :: remove_cvref_t < T >> {});
3. Design considerations
3.1. std :: ranges :: get
Alternative design was considered to name the proposed API .
Given that we discussed this option several times in C++ committee and it didn’t even have a weak support to pursue, authors think that keeping the detailed information is no more necessary. Please see [P2769R2] for more information.
3.2. Breaking code example
With the proposed API some user code might be broken because makes an ADL call underneath.
Consider the following example:
namespace my { struct type {}; template < std :: size_t I , typename ... Args > decltype ( auto ) get ( std :: tuple < Args ... >& t ) { return std :: get < I > ( t ); } } int main () { std :: tuple < my :: type > t ; // In the standard std::apply uses std::get // If std::apply would use std::get_element it's compile-time error because ADL call within get_element // will find both std::get and my::get, thus it ends up with ambiguity std :: apply ([]( auto ) { /* do something */ }, t ); }
However, such potential break is very unlikely. In particular, there is no user-defined type involved in the overload in namespace. If such code in namespace was deployed, it’s likely to be already broken.
Indeed, while is not a customization point, it is often used unqualified (and therefore found by ADL),
such that the code with in namespace already leads to ambiguity.
This potential breaking change could be mitigated by checking in the implementation if the function argument type
is one of the four types now works with (ignoring supported only since C++26).
The implementation (with pseudo-code) is provided below for the sake of completeness.
However, the authors do not think any preventive measures are required because of the reasons mentioned above,
and therefore do not recommend this solution.
namespace std { namespace __detail { template < std :: size_t _Ip > struct __get_element_fn { template < typename _TupleLike > auto operator ()( _TupleLike && __tuple_like ) const -> decltype ( auto ) { constexpr bool __get_member_well_formed = requires { std :: forward < _TupleLike > ( __tuple_like ). template get < _Ip > (); }; // using __type = std::remove_cvref_t<_TupleLike>' // if constexpr (__type is std::tuple or std::pair or std::array or // std::ranges::subrange) // { // std::get<_Ip>(std::forward<_TupleLike>(__tuple_like)); // } if constexpr ( __get_member_well_formed ) { return std :: forward < _TupleLike > ( __tuple_like ). template get < _Ip > (); } return get < _Ip > ( std :: forward < _TupleLike > ( __tuple_like )); } }; } // namespace __detail } // namespace std
3.3. get_element for std :: variant and other non-tuple-like types
During the discussion in LEWG (St. Louis, 2024) we got a feedback that it’s better to constraint with and possibly . The idea was to not use it for something that is not , e.g., .
The current proposal purposefully does not consider to be applicable in all the same contexts
where is used, for the following reasons:
-
has overloads that allow to accessstd :: get elements by type, whiletuple doesn’t.std :: get_element -
It is hard to imagine generic code that works with either
ortuple - like because it is always safe to access any element of astd :: variant object whereas fortuple - like only one alternative is "active" and safe to access. Restrictingstd :: variant can therefore prevent potential UB in generic code.get_element
Moreover, could use to require that the specified element index is within the size
of the given type, not relying on to have such check.
Thus, the recommendation is to constrain with . It should be sufficient without ; structured binding does the same.
4. Connections with other papers
4.1. Connection with [P2547R1]
[P2547R1] uses as the example and a good candidate to be a customizable function.
Authors plan to ship the customizable functions proposal first and deal with customizing
standard library functions later. That means we should not expect that examples in this paper
automatically would be transformed to customizable functions when it will land.
Moreover, at this time the authors of [P2547R1] don’t see how to introduce customizable functions
with the same names (e.g. ) without the ABI break, so they will likely need to choose
different names.
4.2. Connection with [P2141R1]
[P2141R1]'s main goal is allow aggregates being interpreted as . At the same time, it
touches the concept making it as generic as for the types structured binding can work with.
It also adds yet another overload that works with any object except those that
are already in the namespace.
With [P2141R1] being adopted does the right thing and works with object, so we
may use just within the implementation of instead of the unqualified call.
Independently of [P2141R1] brings its own value by covering the described motivation use-cases.
Furthermore, in the standard there are already precedences of having two similar things with slightly different
semantics, for example, and , where the latter is not even a CPO.
[P2141R1] also gives another way to generalize the concept (via structured binding).
5. Further discussion
-
Broader implementation experience (should be done before meeting in Poland, 2024)
-
Make
a public concepttuple - like -
Consider requiring the index for
to be within the size of the tupleget_element
6. Formal wording
Below, substitute the � character with a number the editor finds appropriate for the table, paragraph, section or sub-section.
6.1. Modify Concept tuple-like [tuple.like]
template < typename T > concept has - tuple - size = // exposition only requires { typename tuple_size < T >:: type ; }; template < class T , size_t I > concept can - get - tuple - element = // exposition only has - tuple - size < remove_cvref_t < T >> && requires ( T && t ) { typename tuple_element_t < I , remove_cvref_t < T >> ; { get_element < I > ( std :: forward < T > ( t )) } -> convertible_to < const tuple_element_t < I , remove_cvref_t < T >>&> ; }; template < typename T > concept tuple - like = see - below // exposition only has - tuple - size < remove_cvref_t < T >> && [] < size_t ... I > ( index_sequence < I ... > ) { return (... && can - get - tuple - element < T , I > ); } ( make_index_sequence < tuple_size_v < remove_cvref_t < T >>> {}); A typemodels and satisfies the exposition-only concept tuple-like ifT is a specialization ofremove_cvref_t < T > ,array ,complex ,pair , ortuple .ranges :: subrange
6.2. Modify Header < tuple > synopsis [tuple.syn]
[...]// [tuple.helper], tuple helper classes template < class T > constexpr size_t tuple_size_v = tuple_size < T >:: value ; inline namespace /* unspecified */ { template < size_t I > inline constexpr /* unspecified */ get_element = /* unspecified */ ; } inline constexpr auto get_key = get_element < 0 > ; inline constexpr auto get_value = get_element < 1 > ;
6.3. Add the following sections into [tuple]
[...]
� Element access [tuple.elem]
� Customization Point Objects [tuple.cust] �[tuple.cust.get_elem]get_element
6.4. Add the following wording into [tuple.cust.get_elem]
The namedenotes a customization point object ([customization.point.object]). Given an integral constantget_element of typeI and a subexpressionsize_t having typeE :T
If
does not denote a valid type,typename tuple_size < remove_cvref_t < T >>:: type is ill-formed.get_element < I > ( E ) Otherwise, if
is a valid expression,E . get < I > () is expression equivalent toget_element < I > ( E ) E . get < I > () Otherwise, if
has class type andE is a valid expression where the meaning ofget < I > ( E ) is established as-if by performing argument-dependent lookup only ([basic.lookup.argdep]),get is expression equivalent toget_element < I > ( E ) get < I > ( E ) Otherwise,
is ill-formed.get_element < I > ( E )
6.5. Add feature test macro to the end of [version.syn]
[...]#define __cpp_lib_get_element_customization_point 20����L // also in <tuple> , <utility> , <array> , <ranges> [...]
6.6. Modify tuple construct [tuple.cnstr]
template < tuple - like UTuple >
constexpr explicit ( see below ) tuple ( UTuple && u ); Let
be the packI .0 , 1 , …, ( sizeof ...( Types ) - 1 ) Constraints:
([range.utility.helpers]) isdifferent - from < UTuple , tuple > true,
is not a specialization ofremove_cvref_t < UTuple > ,ranges :: subrange
equalssizeof ...( Types ) ,tuple_size_v < remove_cvref_t < UTuple >>
( is_constructible_v < Types , decltype ( get _element is< I > ( std :: forward < UTuple > ( u ))) > && ...) true, andeither
is not 1, or (whensizeof ...( Types ) expands toTypes ... )T andis_convertible_v < UTuple , T > are bothis_constructible_v < T , UTuple > false.Effects: For all i, initializes the ith element of
with* this get _element < i > ( std :: forward < UTuple > ( u )). Remarks: The expression inside explicit is equivalent to:
! ( is_convertible_v < decltype ( get _element The constructor is defined as deleted if< I > ( std :: forward < UTuple > ( u ))), Types > && ...) ( reference_constructs_from_temporary_v < Types , decltype ( get _element is< I > ( std :: forward < UTuple > ( u ))) > || ...) true.
6.7. Modify tuple assignment [tuple.assign]
template < tuple - like UTuple >
constexpr tuple & operator = ( UTuple && u ); Constraints:
([range.utility.helpers]) isdifferent - from < UTuple , tuple > true,
is not a specialization ofremove_cvref_t < UTuple > ,ranges :: subrange
equalssizeof ...( Types ) , and,tuple_size_v < remove_cvref_t < UTuple >>
is_assignable_v < Ti & , decltype ( get _element is< i > ( std :: forward < UTuple > ( u ))) > truefor all i.Effects: For all i, assigns
get _element to< i > ( std :: forward < UTuple > ( u )) get _element .< i > ( * this ) Returns:
.* this
template < tuple - like UTuple >
constexpr const tuple & operator = ( UTuple && u ) const ; Constraints:
([range.utility.helpers]) isdifferent - from < UTuple , tuple > true,
is not a specialization ofremove_cvref_t < UTuple > ,ranges :: subrange
equalssizeof ...( Types ) , and,tuple_size_v < remove_cvref_t < UTuple >>
is_assignable_v < const Ti & , decltype ( get _element is< i > ( std :: forward < UTuple > ( u ))) > truefor all i.Effects: For all i, assigns
get _element to< i > ( std :: forward < UTuple > ( u )) get _element .< i > ( * this ) Returns:
.* this
6.8. Modify tuple_cat in tuple creation [tuple.creation]
template < tuple - like ... Tuples >
constexpr tuple < CTypes ... > tuple_cat ( Tuples && ... tpls ); Let n be
. For every integersizeof ...( Tuples ) :0 <= i < n
Let
be the ith type inTi .Tuples Let
beUi .remove_cvref_t < Ti > Let
be the ith element in the function parameter packtpi .tpls Let
beSi .tuple_size_v < Ui > Let
beEki .tuple_element_t < k , Ui > Let
beeki get _element .< k > ( std :: forward < Ti > ( tpi )) Let
be a pack of the typesElemsi .E0i ,..., ESi −1 i Let
be a pack of the expressionselemsi ,...,e0i .eSi −1 i The types in
are equal to the ordered sequence of the expanded packs of typesCTypes . LetElems0 ..., Elems1 ..., ..., Elemsn −1. .. be the ordered sequence of the expanded packs of expressionscelems .elems0 ..., ..., elemsn −1. .. Mandates:
is( is_constructible_v < CTypes , decltype ( celems ) > && ...) true.Returns:
tuple < CTypes ... > ( celems ...)
6.9. Modify apply in [tuple.apply]
template < class F , tuple - like Tuple >
constexpr decltype ( auto ) apply ( F && f , Tuple && t ) noexcept ( see below ); Effects: Given the exposition-only function template:
namespace std { template < class F , tuple - like Tuple , size_t ... I > constexpr decltype ( auto ) apply - impl ( F && f , Tuple && t , index_sequence < I ... > ) { // exposition only return INVOKE ( std :: forward < F > ( f ) , get _element < I > ( std :: forward < Tuple > ( t ))...); // see [func.require] } } Equivalent to:
return apply - impl ( std :: forward ( f ), std :: forward ( t ), make_index_sequence < tuple_size_v < remove_reference_t < Tuple >>> {}); Remarks: Let
be the packI . The exception specification is equivalent to:0 , 1 , ..., ( tuple_size_v < remove_reference_t < Tuple >> - 1 ) noexcept ( invoke ( std :: forward < F > ( f ), get _element < I > ( std :: forward < Tuple > ( t ))...))
template < class T , tuple - like Tuple > constexpr T make_from_tuple ( Tuple && t ); Mandates: If
is 1, thentuple_size_v < remove_reference_t < Tuple >>
reference_constructs_from_temporary_vT , decltype ( get _element is< 0 > ( declval < Tuple > ())) > false.Effects: Given the exposition-only function template:
namespace std { template < class T , tuple - like Tuple , size_t ... I > requires is_constructible_v < T , decltype ( get _element < I > ( declval < Tuple > ()))... > constexpr T make - from - tuple - impl ( Tuple && t , index_sequence < I ... > ) { // exposition only return T ( get _element < I > ( std :: forward < Tuple > ( t ))...); } }
6.10. Modify relation operators in [tuple.rel]
template < class ... TTypes , class ... UTypes > constexpr bool operator == ( const tuple < TTypes ... >& t , const tuple < UTypes ... >& u ); template < class ... TTypes , tuple - like UTuple > constexpr bool operator == ( const tuple < TTypes ... >& t , const UTuple & u ); For the first overload let
beUTuple .tuple < UTypes ... > Constraints: For all
, wherei ,0 <= i < sizeof ...( TTypes ) get < i > ( t ) == get _element is a valid expression and< i > ( u ) decltype ( get < i > ( t ) == get _element models boolean-testable.< i > ( u )) equalssizeof ...( TTypes ) .tuple_size_v < UTuple > Returns:
trueifget < i > ( t ) == get _element for all< i > ( u ) , otherwisei false.[Note 1: If
equals zero, returnssizeof ...( TTypes ) true. — end note]Remarks:
The elementary comparisons are performed in order from the zeroth index upwards. No comparisons or element accesses are performed after the first equality comparison that evaluates to
false.The second overload is to be found via argument-dependent lookup ([basic.lookup.argdep]) only.
template < class ... TTypes , class ... UTypes > constexpr common_comparison_category_t < synth - three - way - result < TTypes , UTypes > ... > operator <=> ( const tuple < TTypes ... >& t , const tuple < UTypes ... >& u ); template < class ... TTypes , tuple - like UTuple > constexpr common_comparison_category_t < synth - three - way - result < TTypes , Elems > ... > operator <=> ( const tuple < TTypes ... >& t , const UTuple & u ); For the second overload,
denotes the pack of typesElems .tuple_element_t < 0 , UTuple > , tuple_element_t < 1 , UTuple > , …, tuple_element_t < tuple_size_v < UTuple > - 1 , UTuple > Effects: Performs a lexicographical comparison between
andt . Ifu equals zero, returnssizeof ...( TTypes ) .strong_ordering :: equal Otherwise, equivalent to:
if ( auto c = synth - three - way ( get < 0 > ( t ), get _element < 0 > ( u )); c != 0 ) return c ; return ttail <=> utail ; where
for somertail is a tuple containing all but the first element ofr .r Remarks: The second overload is to be found via argument-dependent lookup ([basic.lookup.argdep]) only.
6.11. Modify [range.elements.iterator]
The member typedef-nameis defined if and only if Base modelsiterator_category . In that case,forward_range is defined as follows: Letiterator_category denote the typeC iterator_traits < iterator_t < Base >> :: iterator_category .
If
std :: get _element is an rvalue,< N > ( * current_ ) denotesiterator_category .input_iterator_tag Otherwise, if C models
,derived_from < random_access_iterator_tag > denotesiterator_category random_access_iterator_tag . Otherwise,
denotesiterator_category .C
static constexpr decltype ( auto ) get - element ( const iterator_t < Base >& i ); Effects: Equivalent to:
if constexpr ( is_reference_v < range_reference_t < Base >> ) { return std :: get _element < N > ( * i ); } else { using E = remove_cv_t < tuple_element_t < N , range_reference_t < Base >>> ; return static_cast < E > ( std :: get _element < N > ( * i )); }
6.12. Modify uses_allocator_construction_args in [allocator.uses.construction]
template < class T , class Alloc , pair - like P > constexpr auto uses_allocator_construction_args ( const Alloc & alloc , P && p ) noexcept ; Constraints:
is a specialization ofremove_cv_t < T > andpair is not a specialization ofremove_cvref_t < P > ranges :: subrange . Effects: Equivalent to:
return uses_allocator_construction_args < T > ( alloc , piecewise_construct , forward_as_tuple ( get _element < 0 > ( std :: forward < P > ( p ))), forward_as_tuple ( get _element < 1 > ( std :: forward < P > ( p ))));
6.13. Modify pair in [pairs.pair]
template < class U1 , class U2 > constexpr explicit ( see below ) pair ( pair < U1 , U2 >& p ); template < class U1 , class U2 > constexpr explicit ( see below ) pair ( const pair < U1 , U2 >& p ); template < class U1 , class U2 > constexpr explicit ( see below ) pair ( pair < U1 , U2 >&& p ); template < class U1 , class U2 > constexpr explicit ( see below ) pair ( const pair < U1 , U2 >&& p ); template < pair - like P > constexpr explicit ( see below ) pair ( P && p ); Let
beFWD ( u ) and letstatic_cast < decltype ( u ) > ( u ) beget - elem for the first four overloads andget for the last overload.get_element Constraints:
For the last overload
is not a specialization ofremove_cvref_t < P > ranges :: subrange
isis_constructible_v < T1 , decltype ( get - elem < 0 > ( FWD ( p ))) > true, and
isis_constructible_v < T2 , decltype ( get - elem < 1 > ( FWD ( p ))) > true.Effects: Initializes first with
and second withget - elem < 0 > ( FWD ( p )) .get - elem < 1 > ( FWD ( p )) Remarks: The expression inside explicit is equivalent to:
! is_convertible_v < decltype ( get - elem < 0 > ( FWD ( p ))), T1 > || ! is_convertible_v < decltype ( get - elem < 1 > ( FWD ( p ))), T2 > The constructor is defined as deleted if
reference_constructs_from_temporary_v < first_type , decltype ( get - elem < 0 > ( FWD ( p ))) > || reference_constructs_from_temporary_v < second_type , decltype ( get - elem < 1 > ( FWD ( p ))) > is
true.
template < pair - like P > constexpr pair & operator = ( P && p ); Constraints:
([range.utility.helpers]) isdifferent - from < P , pair > true,
is not a specialization ofremove_cvref_t < P > ,ranges :: subrange
isis_assignable_v < T1 & , decltype ( get _element < 0 > ( std :: forward < P > ( p ))) > true, and
isis_assignable_v < T2 & , decltype ( get _element < 1 > ( std :: forward < P > ( p ))) > true.Effects: Assigns
toget _element < 0 > ( std :: forward < P > ( p )) andfirst toget _element < 1 > ( std :: forward < P > ( p )) . Returns:second .* this
template < pair - like P > constexpr const pair & operator = ( P && p ) const ; Constraints:
([range.utility.helpers]) isdifferent - from < P , pair > true,
is not a specialization ofremove_cvref_t < P > ,ranges :: subrange
isis_assignable_v < const T1 & , decltype ( get _element < 0 > ( std :: forward < P > ( p ))) > true, and
isis_assignable_v < const T2 & , decltype ( get _element < 1 > ( std :: forward < P > ( p ))) > true.Effects: Assigns
toget _element < 0 > ( std :: forward < P > ( p )) andfirst toget _element < 1 > ( std :: forward < P > ( p )) . Returns:second .* this
6.14. Modify "Exposition-only helpers" in [mdspan.sub.helpers]
template < class T > constexpr T de - ice ( T val ) { return val ; } template < integral - constant - like T > constexpr auto de - ice ( T ) { return T :: value ; } template < class IndexType , size_t k , class ... SliceSpecifiers > constexpr IndexType first_ ( SliceSpecifiers ... slices ); Mandates:
is a signed or unsigned integer type.IndexType Let ϕk denote the following value:
sk if Sk models
convertible_to < IndexType > ; otherwise,
if Sk modelsget _element < 0 > ( sk ) ;index - pair - like < IndexType > otherwise,
if Sk is a specialization ofde - ice ( sk . offset ) ;strided_slice otherwise,
.0 Preconditions: ϕk is representable as a value of type
.IndexType Returns:
.extents < IndexType > :: index - cast ( ϕk ) template < size_t k , class Extents , class ... SliceSpecifiers > constexpr auto last_ ( const Extents & src , SliceSpecifiers ... slices ); Mandates:
is a specialization of extents. LetExtents be typenameindex_type Let λk denote the following value:Extents :: index_type .
if Sk modelsde - ice ( sk ) + 1 otherwiseconvertible_to < index_type > ;
if Sk modelsget _element < 1 > ( sk ) ; otherwiseindex - pair - like < index_type >
if Sk is a specialization ofde - ice ( sk . offset ) + de - ice ( sk . extent ) ; otherwisestrided_slice
.src . extent ( k ) Preconditions: λk is representable as a value of type
.index_type Returns:
.Extents :: index - cast ( λk )
7. Revision history
7.1. R2 => R3
-
Remove the details about
design alternativestd :: ranges :: get -
Align the behavior of
with structured bindingget_element -
Add support for value categories for tuple-like concept
-
Modify
andpair - like to useindex - pair - like get_element -
Constrain
withget_element tuple_size
7.2. R1 => R2
-
Add extra motivation to allow user-defined tuples in the standard
-
Propose changes to tuple-like concept with wording
7.3. R0 => R1
-
Address the "structured binding unused variables" questions with [P2169R3]
-
Add ruminations about possible relaxation of the tuple-like concept
-
Apply an approach to minimize ABI and API breaking for the
namestd :: ranges :: get -
Add wording and a feature test macro for the
APIstd :: get_element
8. Polls
8.1. SG9 polls, Issaquah 2023
POLL: The solution proposed in the paper "P2769: customization point object" should be renamed to .
| SF | F | N | A | SA |
|---|---|---|---|---|
| 1 | 2 | 1 | 2 | 1 |
POLL: The solution proposed in the paper "P2769: customization point object" should be moved out of the namespace ().
| SF | F | N | A | SA |
|---|---|---|---|---|
| 2 | 4 | 0 | 1 | 0 |
8.2. Library Evolution Telecon 2024-01-23
POLL: [P2769R1] ( customization point object) needs to allow for user tuple-likes before it can ship
| SF | F | N | A | SA |
|---|---|---|---|---|
| 3 | 3 | 4 | 2 | 0 |
POLL: LEWG should spend more time on [P2769R1] ( customization point object)
| SF | F | N | A | SA |
|---|---|---|---|---|
| 5 | 4 | 0 | 2 | 0 |
8.3. Library Evolution, St. Louis 2024
POLL: Remove the and variables.
Outcome: Unanimous dissent.
POLL: Rename and to and , respectively.
Outcome: Unanimous dissent.
POLL: should align with the structured binding protocol by attempting to find member functions named .
| SF | F | N | A | SA |
|---|---|---|---|---|
| 5 | 5 | 1 | 2 | 0 |
9. Acknowledgements
-
Thanks to Casey Carter for providing
trick. See [P2769R2] for more details.adl_hook -
Thanks to Corentin Jabot for continuous contribution that improved
proposal.get_element -
Thanks to Benjamin Brock for paper review and design discussions.
-
Thanks to Mark Hoemmen for confirming wording changes for
.submdspan