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 if
T is a specialization of
remove_cvref_t < T > ,
array ,
complex ,
pair , or
tuple .
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 constant
get_element of type
I and a subexpression
size_t having type
E :
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 to
get_element < I > ( E )
E . get < I > () Otherwise, if
has class type and
E is a valid expression where the meaning of
get < I > ( E ) is established as-if by performing argument-dependent lookup only ([basic.lookup.argdep]),
get is expression equivalent to
get_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 pack
I .
0 , 1 , …, ( sizeof ...( Types ) - 1 ) Constraints:
([range.utility.helpers]) is
different - from < UTuple , tuple > true
,
is not a specialization of
remove_cvref_t < UTuple > ,
ranges :: subrange
equals
sizeof ...( 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 (when
sizeof ...( Types ) expands to
Types ... )
T and
is_convertible_v < UTuple , T > are both
is_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]) is
different - from < UTuple , tuple > true
,
is not a specialization of
remove_cvref_t < UTuple > ,
ranges :: subrange
equals
sizeof ...( Types ) , and,
tuple_size_v < remove_cvref_t < UTuple >>
is_assignable_v < Ti & , decltype ( get
_element is
< i > ( std :: forward < UTuple > ( u ))) > true
for 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]) is
different - from < UTuple , tuple > true
,
is not a specialization of
remove_cvref_t < UTuple > ,
ranges :: subrange
equals
sizeof ...( Types ) , and,
tuple_size_v < remove_cvref_t < UTuple >>
is_assignable_v < const Ti & , decltype ( get
_element is
< i > ( std :: forward < UTuple > ( u ))) > true
for 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 integer
sizeof ...( Tuples ) :
0 <= i < n
Let
be the ith type in
Ti .
Tuples Let
be
Ui .
remove_cvref_t < Ti > Let
be the ith element in the function parameter pack
tpi .
tpls Let
be
Si .
tuple_size_v < Ui > Let
be
Eki .
tuple_element_t < k , Ui > Let
be
eki
get
_element .
< k > ( std :: forward < Ti > ( tpi )) Let
be a pack of the types
Elemsi .
E0i ,..., ESi −1 i Let
be a pack of the expressions
elemsi ,...,
e0i .
eSi −1 i The types in
are equal to the ordered sequence of the expanded packs of types
CTypes . Let
Elems0 ..., Elems1 ..., ..., Elemsn −1. .. be the ordered sequence of the expanded packs of expressions
celems .
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 pack
I . 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, then
tuple_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
be
UTuple .
tuple < UTypes ... > Constraints: For all
, where
i ,
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 )) equals
sizeof ...( TTypes ) .
tuple_size_v < UTuple > Returns:
true
if
get < i > ( t ) == get
_element for all
< i > ( u ) , otherwise
i false
.[Note 1: If
equals zero, returns
sizeof ...( 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 types
Elems .
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
and
t . If
u equals zero, returns
sizeof ...( 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 some
rtail is a tuple containing all but the first element of
r .
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 models
iterator_category . In that case,
forward_range is defined as follows: Let
iterator_category denote the type
C
iterator_traits < iterator_t < Base >> :: iterator_category .
If
std :: get
_element is an rvalue,
< N > ( * current_ ) denotes
iterator_category .
input_iterator_tag Otherwise, if C models
,
derived_from < random_access_iterator_tag > denotes
iterator_category
random_access_iterator_tag . Otherwise,
denotes
iterator_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 of
remove_cv_t < T > and
pair is not a specialization of
remove_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
be
FWD ( u ) and let
static_cast < decltype ( u ) > ( u ) be
get - elem for the first four overloads and
get for the last overload.
get_element Constraints:
For the last overload
is not a specialization of
remove_cvref_t < P >
ranges :: subrange
is
is_constructible_v < T1 , decltype ( get - elem < 0 > ( FWD ( p ))) > true
, and
is
is_constructible_v < T2 , decltype ( get - elem < 1 > ( FWD ( p ))) > true
.Effects: Initializes first with
and second with
get - 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]) is
different - from < P , pair > true
,
is not a specialization of
remove_cvref_t < P > ,
ranges :: subrange
is
is_assignable_v < T1 & , decltype ( get _element < 0 > ( std :: forward < P > ( p ))) > true
, and
is
is_assignable_v < T2 & , decltype ( get _element < 1 > ( std :: forward < P > ( p ))) > true
.Effects: Assigns
to
get _element < 0 > ( std :: forward < P > ( p )) and
first to
get _element < 1 > ( std :: forward < P > ( p )) . Returns:
second .
* this
template < pair - like P > constexpr const pair & operator = ( P && p ) const ; Constraints:
([range.utility.helpers]) is
different - from < P , pair > true
,
is not a specialization of
remove_cvref_t < P > ,
ranges :: subrange
is
is_assignable_v < const T1 & , decltype ( get _element < 0 > ( std :: forward < P > ( p ))) > true
, and
is
is_assignable_v < const T2 & , decltype ( get _element < 1 > ( std :: forward < P > ( p ))) > true
.Effects: Assigns
to
get _element < 0 > ( std :: forward < P > ( p )) and
first to
get _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 models
get _element < 0 > ( sk ) ;
index - pair - like < IndexType > otherwise,
if Sk is a specialization of
de - 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. Let
Extents be typename
index_type Let λk denote the following value:
Extents :: index_type .
if Sk models
de - ice ( sk ) + 1 otherwise
convertible_to < index_type > ;
if Sk models
get _element < 1 > ( sk ) ; otherwise
index - pair - like < index_type >
if Sk is a specialization of
de - ice ( sk . offset ) + de - ice ( sk . extent ) ; otherwise
strided_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