1. Introduction
The tuple protocol has been introduced in C++11.
If
is a tuplelike type:

It can be destructured into
elements;std :: tuple_size_v < T > 
Its
th element has typeI
and can be extracted through thestd :: tuple_element_t < I , T >
function template.std :: get
Since C++17, the tuple protocol interacts with the core language, which allows structured bindings to expressions of tuplelike types.
The standard already mandates the following types to be tuplelike:

(since C++20),std :: ranges :: subranges 
(since C++11),std :: tuple 
(since C++11),std :: pair 
and, most relevant to this paper,
(since C++11).std :: array
In this paper, I propose the standard should make Cstyle arrays of known
bound,
, tuplelike too.
The implementation of the tuplelike protocol (i.e.
,
and
) I propose is designed after the existing
one for
.
2. Motivation
As far as their tuplelike properties are concerned,
and
are equivalent.
Both have a fixed number of elements,
, which is known at compiletime; each
element being of type
and accessible by a compiletime index.
Implementing the tuplelike protocol for Cstyle arrays would make them eligible to be passed as parameters to:
(since C++17),std :: apply
(since C++17)std :: make_from_tuple 
and,
(since C++11)std :: tuple_cat Prior to [P2165R3], the choice whether to support tuplelike types besides
instd :: tuple
was up to the implementers.std :: tuple_cat
In sections § 2.1 Automatic size deduction and § 2.2 Interacting with C APIs I outline some use cases where
may be preferable over
.
In such cases, being able to call the above functions without the need for a
temporary
would be beneficial.
2.1. Automatic size deduction
Unlike
, compilers are able to deduce the size (only) of a Cstyle
array from the number of elements in its initializer list:
int c_arr [] = { 0 , 1 , 2 }; static_assert ( sizeof ( c_arr ) / sizeof ( c_arr [ 0 ]) == 3 , "" );
This limitation of
was noted by Alisdair Meredith in [N1479] itself and lead Zhihao Yuan to float the idea of extending the implementation
of the tuple protocol to Cstyle arrays in [ARRAYASATUPLE].
CTAD (since C++17) for
mitigates this problem but doesn’t allow a
user to specify the element type and deduce the size only.
Function template
(since C++20) does but poses constraints on
the element type (namely, it has be copy or moveconstructible and nonarray).
2.2. Interacting with C APIs
C (or Clike) API may force users to deal with Cstyle arrays:
// File: geometry_c_api.h #define GEOMETRY_STATUS_OK 0 struct ReferenceFrame ; int get_origin ( struct ReferenceFrame * frame , double ( * pt )[ 3 ]);
If this paper gets accepted, client code might look like this:
class Point { public : explicit Point ( double x , double y , double z ); // ... }; std :: optional < Point > get_origin ( ReferenceFrame & frame ) { double pt [ 3 ] { }; if ( get_origin ( & frame , & pt ) != GEOMETRY_STATUS_OK ) return { }; return std :: make_from_tuple < Point > ( pt ); }
2.3. Compiletime bound check
A useful side benefit of the tuple protocol is the bound check performed by
function template
:
int c_arr [ 42 ]{}; // This would not not compile because index 42 is out of bounds //std::get<42>(c_arr) = 42; // Not OK: this is UB and compiles c_arr [ 42 ] = 42 ;
Making
tuplelike would help users prevent the above class of bugs
without any need for static analysis tools or sanitizers.
3. Impact on the standard
This proposal is a pure library extension.
It proposes changes to an existing header,
, but it does not require
changes to any standard classes or functions.
This proposal does not require changes in the core language. It does not produce changes in the core language either. Even though the tuple protocol interferes with the core language, which provides structuredbinding support for tuplelike types, the standard already defines special rules for structured bindings to Cstyle arrays.
This proposal does not depend on any other library extension. In section § 4.1 Proposed implementation, I propose an implementation in standard C++11.
3.1. Interaction with other papers
With this proposal,
would satisfy expositiononly concept
introduced by Corentin Jabot with [P2165R3].
So,
s and
s would be constructible from and comparable
with Cstyle arrays:
int c_arr [] = { 0 , 1 }; //std::tuple<int, int, int> t = c_arr; // Error: different tuple size std :: pair < int , int > p = c_arr ; p == c_arr ; // Ok: evaluates to true p < c_arr ; // Ok: evaluates to false
int c_arr [] = { 0 , 1 , 2 }; std :: tuple < int , int , int > t = c_arr ; //std::pair<int, int> p = c_arr; // Error: different tuple size t == c_arr ; // Ok: evaluates to true t < c_arr ; // Ok: evaluates to false
4. Design decisions
4.1. Proposed implementation
In the following subsections, I outline my proposed implementation of the tuple
protocol for Cstyle arrays of known bound in standard C++11.
My implementation is designed after
's.
4.1.1. std :: tuple_size
For the
class template, I propose the following specializations:
namespace std { // (ts) template < typename T , size_t N > struct tuple_size < T [ N ] > : public integral_constant < size_t , N > { }; // (ts.c) template < typename T , size_t N > struct tuple_size < T const [ N ] > : public integral_constant < size_t , N > { }; }
4.1.1.1. const
qualified specialization
Specialization
is required because:

The standard already defines a
qualified specialization forconst
(andstd :: tuple_size
);std :: tuple_element 
Applying cvqualifiers to an array type applies the qualifiers to the element type and any array type whose elements are of cvqualified type is considered to have the same cvqualification [CPPREFARRAY].
So, by not defining
, the following code
using const_array_t = int const [ 42 ]; static_assert ( std :: tuple_size < array_t >:: value == 42 , "Size OK" );
would fail to compile because the template instanciation for
is ambiguous.
In fact both the following specializations would be viable candidates:
namespace std { // Already in the standard template < class T > struct tuple_size < const T > : public integral_constant < size_t , tuple_size < T >:: value > { }; // With T = int[42] // Proposed in this paper template < typename T , size_t N > struct tuple_size < T [ N ] > : public integral_constant < size_t , N > { }; // With T = int const, N = 42 }
4.1.1.2. volatile
qualified specializations
The existing specializations of
(and
)
for
qualified types were deprecated in C++20 and will be removed in
C++23, as per [P1831R1].
So, there is no need to provide specializations of
(nor
) for:

T volatile [ N ] 
.T volatile const [ N ]
4.1.2. std :: tuple_element
For the
class template, I propose the following specializations:
namespace std { // (te) template < size_t Idx , typename T , size_t N > struct tuple_element < Idx , T [ N ] > { static_assert ( Idx < N , "Index out of bounds" ); using type = T ; }; // (te.c) template < size_t Idx , typename T , size_t N > struct tuple_element < Idx , T const [ N ] > { static_assert ( Idx < N , "Index out of bounds" ); using type = T const ; }; }
4.1.2.1. cvqualified specializations
The reasoning behind the introduction of specialization
in § 4.1.1 std::tuple_size also motivates the
introduction of specialization
here.
As for the
qualified specializations of
, I
explain why there’s no need for those in section § 4.1.1.2 volatilequalified specializations.
Please note however that, by my proposal, the following code would compile:
using v_array_t = int volatile [ 42 ]; using cv_array_t = int volatile const [ 42 ]; static_assert ( std :: is_same_v < std :: tuple_element_t < 0 , v_array_t > , int volatile > , "" ); static_assert ( std :: tuple_size_v < v_array_t > == 42 , "" ); static_assert ( std :: is_same_v < std :: tuple_element_t < 0 , cv_array_t > , int volatile const > , "" ); static_assert ( std :: tuple_size_v < cv_array_t > == 42 , "" );
I believe this to be correct.
While none of the above
s would compile after [P1831R1] with:
using v_array_t = std :: array < int , 42 > volatile ; using cv_array_t = std :: array < int , 42 > volatile const ;
They would both compile with:
using v_array_t = std :: array < int volatile , 42 > ; using cv_array_t = std :: array < int volatile const , 42 > ;
4.1.3. std :: get
For the
function templates, I propose:
namespace std { template < size_t Idx , typename T , size_t N > constexpr T & get ( T ( & arr )[ N ]) noexcept { static_assert ( Idx < N , "Index out of bounds" ); return arr [ Idx ]; } template < size_t Idx , typename T , size_t N > constexpr T && get ( T ( && arr )[ N ]) noexcept { static_assert ( Idx < N , "Index out of bounds" ); return move ( arr [ Idx ]); } }
4.2. Alternative implementation
Another possible implementation for the Cstyle array specializations of class
templates
and
in C++20 is the following
(courtesy of Arthur O’Dwyer):
namespace std { template < typename T , size_t N > requires ( is_same_v < T , remove_const_t < T >> ) struct tuple_size < T [ N ] > : public integral_constant < size_t , N > {}; template < size_t Idx , typename T , size_t N > requires ( is_same_v < T , remove_const_t < T >> ) struct tuple_element < Idx , T [ N ] > { static_assert ( Idx < N , "Index out of bounds" ); using type = T ; }; }
The
clause SFINAEs out the specializations of
and
for
qualified array types and prevents the
ambiguoustemplateinstantiation compilation error described in section § 4.1.1 std::tuple_size.
As for the
function templates, implementing them by means of the
clause is not feasible.
In fact, the following:
namespace std { template < size_t Idx , typename T , size_t N > requires ( Idx < N ) constexpr T & get ( T ( & arr )[ N ]) noexcept { return arr [ Idx ]; } template < size_t Idx , typename T , size_t N > requires ( Idx < N ) constexpr T && get ( T ( && arr )[ N ]) noexcept { return move ( arr [ Idx ]); } }
may lead to an inconsistent behavior with the existing implementation for
in unevaluated contexts:
std :: array < int , 42 > cpp_arr {}; using cpp_elem_ptr_t = decltype ( & std :: get < 42 > ( cpp_arr )); // cpp_elem_ptr_t is int* //int c_arr[42]{}; //using c_elem_ptr_t = decltype(&std::get<42>(c_arr)); // error: no matching function for call to 'get<42>(int [42])'
5. Technical specifications
In this section, I present the changes I propose to the standard. The wording is based on [N4910].
Modify section "Header
synopsis
":
// 22.4.6, tuple helper classes template < class T > struct tuple_size ; // not defined template < class T > struct tuple_size < const T > ; template < class ... Types > struct tuple_size < tuple < Types ... >> ;
template < class T , size_t N > struct tuple_size < T [ N ] > ; template < class T , size_t N > struct tuple_size < T const [ N ] > ;
template < size_t I , class T > struct tuple_element ; // not defined template < size_t I , class T > struct tuple_element < I , const T > ; template < size_t I , class ... Types > struct tuple_element < I , tuple < Types ... >> ;
template < size_t I , class T , size_t N > struct tuple_element < I , T [ N ] > ; template < size_t I , class T , size_t N > struct tuple_element < I , T const [ N ] > ;
template < size_t I , class T > using tuple_element_t = typename tuple_element < I , T >:: type ; // 22.4.7, element access template < size_t I , class ... Types > constexpr tuple_element_t < I , tuple < Types ... >>& get ( tuple < Types ... >& ) noexcept ; ... template < class T , class ... Types > constexpr const T && get ( const tuple < Types ... >&& t ) noexcept ;
template < size_t I , class T , size_t N > constexpr T & get ( T ( & arr )[ N ]) noexcept ; template < size_t I , class T , size_t N > constexpr T && get ( T ( && arr )[ N ]) noexcept ;
Modify section "Tuple helper classes
":
template < class T > struct tuple_size ;
^{1} All specializations of
meet the Cpp17UnaryTypeTrait requirements (21.3.2) with a base
characteristic of
for some
.
template < class ... Types > struct tuple_size < tuple < Types ... >> : public integral_constant < size_t , sizeof ...( Types ) > { };
template < class T , size_t N > struct tuple_size < T [ N ] > : public integral_constant < size_t , N > { }; template < class T , size_t N > struct tuple_size < T const [ N ] > : public integral_constant < size_t , N > { };
^{2} Mandates:template < size_t I , class ... Types > struct tuple_element < I , tuple < Types ... >> { using type = TI ; };
I < sizeof ...( Types )
.^{3} Type:
TI
is the type of the I
th element of Types
, where indexing is zerobased.
^{4} Mandates:template < size_t I , class T , size_t N > struct tuple_element < I , T [ N ] > { using type = T ; };
I < N
.
^{5} Mandates:template < size_t I , class T , size_t N > struct tuple_element < Idx , T const [ N ] > { using type = T const ; };
I < N
.
Append to section "Element access
":
^{9} Mandates:template < size_t I , class T , size_t N > constexpr T & get ( T ( & arr )[ N ]) noexcept ; template < size_t I , class T , size_t N > constexpr T && get ( T ( && arr )[ N ]) noexcept ;
I < N
.^{10} Returns: A reference to the
I
th element of arr
, where indexing is zerobased.
6. Acknowledgements
I’d like to thank (sorted by
)

Arthur O’Dwyer,

Barry Revzin,

Giuseppe D’Angelo,

Jason McKesson,

Jens Maurer,

Lénárd Szolnoki,

Nikolay Mihaylov,

Zhihao Yuan
for their valuable feedbacks which made this paper possible.