Document number: N3794 Project: Programming Language C++, Library Working Group Date: 2013-10-10 Reply-to: Daryle Walker <darylew at gmail dot com>
std::array
The std::array
class template adapts array types to standard
library classes. But the adaptation is only for the outermost level; the inner
dimensions of a multidimensional array type are not considered. This proposal
is to change std::array
to take multiple dimensions as variadic
template arguments.
When reading up on the variadic template feature, I made a multidimensional
variant of std::array
as an exercise. After making the
single-extent case compatible with std::array
, I realized it is
better to propose a change in std::array
instead of creating a
near-identical class template. The exercise code, at <https://github.com/CTMacUser/ArrayMD>,
could be considered as a reference implementation and test suite.
I later found out that a multidimensional array class template was one of the examples given in the original variadic template proposal.
With constructions that can express array types and array subscript usage in comma-separated lists, array entities may be invoked during variadic template- and function-argument processing.
The proposal adds two class templates (and corresponding type-alias
templates), three function templates, and several members to an existing class
template. It modifies descriptions of std::array
in the sequence
container requirements. And it modifies the relational operator function
templates to take mixed element types. The implementation should work with
current language features.
I adapted std::array
for multidimensional arrays by making the
inner array object a built-in multidimensional array type. Since built-in types
are used, only the first level of access via operator []()
has to
be a function instead of a built-in operation. Similar multidimensional
container libraries where the array class template consists of nested
instantiations of itself have the disadvantages of possible padding (making the
elements noncontiguous) and of working in the wrong units (the largest
sub-array type instead of the user's original element type).
The implementation is mostly as straight forward as the current
std::array
, so there is not much effort needed from
implementers.
The changes are based off C++ Working Draft Standard N3691. The section numbers for new sections have letters as place-holders; the final numbers, plus moving existing section numbers to fit, are to be determined later.
In section 20.11.2 [meta.type.synop], change the subsection introducing array modifications:
// 20.11.7.4, array modifications: template <class T> struct remove_extent; template <class T> struct remove_all_extents; template <class T, size_t I> struct remove_some_extents; template <class T, size_t... N> struct append_extents; template <class T> using remove_extent_t = typename remove_extent<T>::type; template <class T> using remove_all_extents_t = typename remove_all_extents<T>::type; template <class T, size_t I> using remove_some_extents_t = typename remove_some_extents<T, I>::type; template <class T, size_t... N> using append_extents_t = typename append_extents<T, N...>::type;
In section 20.11.7.4 [meta.trans.arr], add a third and fourth row to Table 55:
Addition to: Table 55 — Array modifications Template Comments template <class T, size_t I> struct remove_some_extents;If I
is zero, then the member typedeftype
shall beT
. Otherwise, the member typedeftype
shall be the same as applyingremove_extent
I
times. [Note: WhenT
is either a non-array type or an array type with an extent count at mostI
, this class template acts likeremove_all_extents
. —end note] template <class T, size_t... N> struct append_extents;If N
is an empty parameter pack, then the member typedeftype
shall beT
. Otherwise, letX
be the first entry ofN
,Y
be a (possibly empty) parameter pack representing the remainder ofN
, andU
be an alias totypename append_extents<T, Y...>::type
. Then the member typedeftype
shall aliasU[X]
whenX
is non-zero, else it shall aliasU[]
. [Note: Since an array of unknown bound is not permitted to be an array element type (8.3.4), only the first (i.e. left-most) entry ofN
may be zero. —end note]
and add a third example:
-3- [Example
// the following assertions hold: assert((is_same<remove_some_extents<int, 0>::type, int>::value)); assert((is_same<remove_some_extents<int, 1>::type, int>::value)); assert((is_same<remove_some_extents<int[2], 0>::type, int[2]>::value)); assert((is_same<remove_some_extents<int[2], 1>::type, int>::value)); assert((is_same<remove_some_extents<int[2], 2>::type, int>::value)); assert((is_same<remove_some_extents<int[2][3], 0>::type, int[2][3]>::value)); assert((is_same<remove_some_extents<int[2][3], 1>::type, int[3]>::value)); assert((is_same<remove_some_extents<int[2][3], 2>::type, int>::value)); assert((is_same<remove_some_extents<int[][3], 0>::type, int[][3]>::value)); assert((is_same<remove_some_extents<int[][3], 1>::type, int[3]>::value)); assert((is_same<remove_some_extents<int[][3], 2>::type, int>::value)); assert((is_same<remove_some_extents<int[][3], 3>::type, int>::value));—end example]
In section 23.2.3 [sequence.reqmts], modify the last column for the last two rows of Table 101:
Modification to: Table 101 — Optional sequence container operations (continued) Expression Return type Operational semantics Container a[n]
reference
;const_reference
for constanta
*(a.begin() + n)
basic_string
,array
(with one extent),deque
,dynarray
,vector
a.at(n)
reference
;const_reference
for constanta
*(a.begin() + n)
basic_string
,array
(with one extent),deque
,dynarray
,vector
and modify the paragraph that follows that table:
-17- The member function
at()
provides bounds-checked access to container elements.at()
throwsout_of_range
ifn >= a.size()
. For instantiations ofarray
with an extent count besides one, there are modifications (23.3.2.D) to the return types and the operational semantics ofa[n]
anda.at()
.
In section 23.3.1 [sequences.general], modify the "Header
<array>
synopsis":
#include <initializer_list> #include <type_traits> namespace std { template <class T, size_t... N > struct array; template <class T, class U, size_t... N> bool operator==(const array<T,N...>& x, const array<TU,N...>& y); template <class T, class U, size_t... N> bool operator!=(const array<T,N...>& x, const array<TU,N...>& y); template <class T, class U, size_t... N> bool operator<(const array<T,N...>& x, const array<TU,N...>& y); template <class T, class U, size_t... N> bool operator>(const array<T,N...>& x, const array<TU,N...>& y); template <class T, class U, size_t... N> bool operator<=(const array<T,N...>& x, const array<TU,N...>& y); template <class T, class U, size_t... N> bool operator>=(const array<T,N...>& x, const array<TU,N...>& y); template <class T, size_t... N > void swap(array<T,N...>& x, array<T,N...>& y) noexcept(noexcept(x.swap(y))); template <class T> class tuple_size; template <size_t I, class T> class tuple_element; template <class T, size_t... N> struct tuple_size<array<T, N...> >; template <size_t I, class T, size_t... N> struct tuple_element<I, array<T, N...> >; template <size_t I, class T, size_t... N> constexpr T& get(array<T, N...>&) noexcept; template <size_t I, class T, size_t... N> constexpr T&& get(array<T, N...>&&) noexcept; template <size_t I, class T, size_t... N> constexpr const T& get(const array<T, N...>&) noexcept; template <class T, size_t... N, class... Args> constexpr array<T, N...> make_array(Args&&...); template <class... Args> constexpr array<common_type_t<Args...>, sizeof...(Args)> make_auto_array(Args&&...); template <class TO, size_t... NO, class TI, size_t... NI> constexpr array<TO, NO...> reshape_array(const array<TI, NI...>&); }
Modify section 23.3.2.1 [array.overview]:
-1- The header
<array>
defines a class template for storing fixed-size sequences of objects. An array supports random access iterators. An instance ofarray<T, N...>
stores
NP
elements of typeT
, whereP
is the product of the entries from the pack expansionN
, so thatsize() ==
NP
is an invariant. The elements of an array are stored contiguously, meaning that ifa
is anarray<T, N...>
then it obeys the identity&((T*)&a)[n] == &((T*)&a)[0] + n
for all0 <= n <
NP
.-2- An
array
is an aggregate (8.5.1) that can be initialized with the syntax
array<T,
NN0, ..., Nsizeof...(N)-1> a = { initializer-list };
where initializer-list is a comma-separated (and possibly braced-partitioned) list of up to
NP
elements whose types are convertible toT
, andP
is the product of the entriesN0
throughNsizeof...(N)-1
from the pack expansionN
. [Note:P
is1
whenN
is empty. —end note]-3- An
array
satisfies all of the requirements of a container and of a reversible container (23.2), except that a default constructedarray
object is not empty and thatswap
does not have constant complexity. Anarray
satisfies some of the requirements of a sequence container (23.2.3). Descriptions are provided here only for operations onarray
that are not described in one of these tables and for operations where there is additional semantic information.namespace std { template <class T, size_t ...N > struct array { // types: typedef append_extents_t<T, N...> type; typedef T& reference; typedef const T& const_reference; typedef implementation-defined iterator; typedef implementation-defined const_iterator; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef reverse_iterator<iterator> reverse_iterator; typedef reverse_iterator<const_iterator> const_reverse_iterator; typedef remove_extent_t<type> dereference; // iff sizeof...(N) > 0Tconditional_t<!sizeof...(N) || extent<type>::value, type, aligned_storage_t<sizeof(T), alignof(T)>> elems[N]; // exposition only static constexpr size_type value_count = sizeof...(N) ? extent<type>::value * sizeof(dereference) / sizeof(value_type) : 1, extent_count = sizeof...(N), extents[] = { N... }; // iff sizeof...(N) > 0 // no explicit construct/copy/destroy for aggregate type void fill(const T& u); void swap(array&) noexcept(noexcept(swap(declval<T&>(), declval<T&>()))); template <class F> void apply(F&& f); template <class F> void apply(F&& f) const; template <class F> void capply(F&& f) const; // iterators: iterator begin() noexcept; const_iterator begin() const noexcept; iterator end() noexcept; const_iterator end() const noexcept; reverse_iterator rbegin() noexcept; const_reverse_iterator rbegin() const noexcept; reverse_iterator rend() noexcept; const_reverse_iterator rend() const noexcept; const_iterator cbegin() const noexcept; const_iterator cend() const noexcept; const_reverse_iterator crbegin() const noexcept; const_reverse_iterator crend() const noexcept; // capacity: constexpr size_type size() const noexcept; constexpr size_type max_size() const noexcept; constexpr bool empty() const noexcept; // element access:referencedereference& operator[](size_type n); // iff sizeof...(N) > 0 constexprconst_referenceconst dereference& operator[](size_type n) const; // iff sizeof...(N) > 0reference at(size_type n); constexpr const_reference at(size_type n) const;template <class ...SizeType> auto operator()(const SizeType&... n) -> remove_some_extents_t<type, sizeof...(SizeType)>&; template <class ...SizeType> constexpr auto operator()(const SizeType&... n) const -> const remove_some_extents_t<type, sizeof...(SizeType)>&; template <class ...SizeType> auto at(const SizeType&... n) -> remove_some_extents_t<type, sizeof...(SizeType)>&; template <class ...SizeType> constexpr auto at(const SizeType&... n) const -> const remove_some_extents_t<type, sizeof...(SizeType)>&; reference operator[](initializer_list<size_type> i); constexpr const_reference operator[](initializer_list<size_type> i) const; reference operator()(initializer_list<size_type> i); constexpr const_reference operator()(initializer_list<size_type> i) const; reference at(initializer_list<size_type> i); constexpr const_reference at(initializer_list<size_type> i) const; reference front(); constexpr const_reference front() const; reference back(); constexpr const_reference back() const;T *pointer data() noexcept;const T *const_pointer data() const noexcept; }; }-4- [ Note: The member variable
elems
is shown for exposition only, to emphasize thatarray
is a class aggregate. The nameelems
is not part ofarray
’s interface. —end note ]
No modifications for section 23.3.2.2 [array.cons]:
-1- The conditions for an aggregate (8.5.1) shall be met. Class
array
relies on the implicitly-declared special member functions (12.1, 12.4, and 12.8) to conform to the container requirements table in 23.2. In addition to the requirements specified in the container requirements table, the implicit move constructor and move assignment operator forarray
require thatT
beMoveConstructible
orMoveAssignable
, respectively.
Modify section 23.3.2.3 [array.special]:
template <class T, size_t... N> void swap(array<T,N...>& x, array<T,N...>& y) noexcept(noexcept(x.swap(y)));-1- Effects:
x.swap(y);
-2- Complexity: linear in
Narray<T,N...>::value_count
.
Add new section 23.3.2.A [array.create], titled "array
creation":
template <class T, size_t... N, class... Args> constexpr array<T, N...> make_array(Args&&... args);-1- Requires:
sizeof...(Args) <= array<T, N...>::value_count
. WhenT
is notDefaultConstructible
,sizeof...(Args) == array<T, N...>::value_count
. Each type withinArgs
is explicitly convertible toT
.-2- Returns: an object that is
- value-initialized when
sizeof...(Args) == 0
,- else braced-initialized with the pack expansion
static_cast<T>( forward<Args>(args) )...
whensizeof...(N) == 0
[Note: The braced list is ill-formed whensizeof...(Args) > 1
(8.5.1). —end note],- else braced-initialized with a braced list of the pack expansion
static_cast<T>( forward<Args>(args) )...
[Note: Unhandled trailing elements get value-initialized. Brace elision occurs whensizeof...(N) > 1
(8.5.1). —end note].template <class... Args> constexpr array<common_type_t<Args...>, sizeof...(Args)> make_auto_array(Args&&... args);-3- Requires:
sizeof...(Args) > 0
.common_type_t<Args...>
is well-formed.-4- Returns:
make_array<common_type_t<Args...>, sizeof...(Args)>( forward<Args>(args)... )
.template <class TO, size_t... NO, class TI, size_t... NI> constexpr array<TO, NO...> reshape_array(const array<TI, NI...>& x);-5- Requires:
TO
isDefaultConstructible
.TI
is explicitly convertible toTO
.-6- Returns: an object
y
such that each*(y.begin() + d)
is equivalent tostatic_cast<TO>(*(x.begin() + d))
for0 <= d < min(x.size(), y.size())
and the trailing elements ofy
, if any, are value-initialized.
Modify section 23.3.2.4 [array.size]:
template <class T, size_t N> constexpr size_type array<T,N>::size() const noexcept;-1- Returns:
Nvalue_count
Modify section 23.3.2.5 [array.data]:
T *pointer data() noexcept;const T *const_pointer data() const noexcept;-1- Returns: When
extent_count == 0
,addressof(elems)
, otherwise the address of the first (possibly nested) element ofT
inelems
.
Modify section 23.3.2.6 [array.fill]:
void fill(const T& u);-1- Effects:
fill_n(begin(),
Nvalue_count, u)
No modifications for section 23.3.2.7 [array.swap]:
void swap(array& y) noexcept(noexcept(swap(declval<T&>(), declval<T&>())));-1- Effects:
swap_ranges(begin(), end(), y.begin())
-2- Throws: Nothing unless one of the element-wise swap calls throws an exception.
-3- Note: Unlike the
swap
function for other containers,array::swap
takes linear time, may exit via an exception, and does not cause iterators to become associated with the other container.
Add new section 23.3.2.B [array.apply]:
template <class F> void apply(F&& f);template <class F> void apply(F&& f) const;template <class F> void capply(F&& f) const;-1- Requires:
f
is compatible withfunction<void(cv T&, size_type, ..., size_type)>
, where cv is the cv-qualification of*this
and the number of trailingsize_type
arguments isextent_count
.-2- Effects:
f
is called once for each element with the following constraints:
- Given element
e
withextent_count
ordered indices i0, ..., ik required to dereference it fromelems
, the application call will be compatible withforward<F>(f)(e, i0, ..., ik)
.- The order of element traversal is implementation-defined.
-3- Throws: Nothing unless an application call throws an exception.
Author's Note: Element traversal order should be the in-memory order or some other order that minimizes cache misses.
Add new section 23.3.2.C [array.iter], titled "array
iteration":
-1- The beginning value of forward iteration points to the element at the address returned by
data()
. The past-the-end value corresponds todata() + value_count
. Propagation of forward iteration follows the (nested) row-wise storage order (8.3.4).
Author's Note: The above section explicitly defines the intuitive
order elements are linearly visited when the array
object is
multidimensional.
Add new section 23.3.2.D [array.access], titled "Element access":
dereference& operator[](size_type n); constexpr const dereference& operator[](size_type n) const;-1- Requires:
sizeof...(N) > 0
andn < extents[0]
.-2- Returns:
elems[n]
.-3- Throws: Nothing.
template <class ...SizeType> auto operator()(const SizeType&... n) -> remove_some_extents_t<type, sizeof...(SizeType)>&; template <class ...SizeType> constexpr auto operator()(const SizeType&... n) const -> const remove_some_extents_t<type, sizeof...(SizeType)>&;-4- Requires:
sizeof...(SizeType) <= extent_count
. Each entry in the pack expansionSizeType
has to have an implicit conversion tosize_type
. Each entry in the pack expansionn
, in order, needs to be less than the corresponding value inextents
.-5- Returns:
elems
followed bysizeof...(n)
calls of the built-in subscript operator, with each operator using an entry from the pack expansionn
, in order, as an index.-6- Throws: Nothing unless a conversion from an entry of the pack expansion
n
to asize_type
value throws an exception.-7- Remarks: If any of the types in the pack expansion
SizeType
cannot be implicitly converted tosize_type
, or ifsizeof...(SizeType) > extent_count
, then that function shall not participate in overload resolution.reference operator[](initializer_list<size_type> i); constexpr const_reference operator[](initializer_list<size_type> i) const; reference operator()(initializer_list<size_type> i); constexpr const_reference operator()(initializer_list<size_type> i) const;-8- Requires:
i.size() == extent_count
. For0 <= d < extent_count
,*(i.begin() + d) < extents[d]
.-9- Returns:
operator()(i0, ..., iextent_count-1)
, where theik
are the elements ofi
, in order.-10- Throws: Nothing.
template <class ...SizeType> auto at(const SizeType&... n) -> remove_some_extents_t<type, sizeof...(SizeType)>&; template <class ...SizeType> constexpr auto at(const SizeType&... n) const -> const remove_some_extents_t<type, sizeof...(SizeType)>&; reference at(initializer_list<size_type> i); constexpr const_reference at(initializer_list<size_type> i) const;-11- Requires: the same requirements as the corresponding version of
operator()()
.-12- Returns:
operator()( X )
, where X is the argument listat()
received.-13- Throws: Nothing unless: a conversion from an entry of the pack expansion
n
to asize_type
value throws an exception; a particular index is equal or greater than its corresponding entry inextents
, in whichout_of_range
is thrown; ori.size() != extent_count
, in whichlength_error
is thrown.-14- Remarks: If any of the types in the pack expansion
SizeType
cannot be implicitly converted tosize_type
, or ifsizeof...(SizeType) > extent_count
, then that function shall not participate in overload resolution.
Author's Note: The contained (array) object, elems
, can
be directly referenced by calling operator()()
, at()
,
or the initializer_list
overload of operator[]()
with
no indexes.
Add new section 23.3.2.E [array.extents], titled "Zero extent arrays":
-1-
array
shall provide support for the special case ofN
being an empty pack expansion.-2- In the case that
sizeof...(N) == 0
, thedereference
andextents
members are not provided. Neither are the overloads ofoperator[]()
that take onesize_type
parameter provided. It is implementation-defined if the overloads ofoperator()()
andat()
that do not take aninitializer_list
are replaced with equivalent non-template members that take zero parameters.
Modify section 23.3.2.8 [array.zero]
-1-
array
shall provide support for the special case. [Note: Such
Nvalue_count == 0array
instantiations are made by using at least one extent and setting the first extent to zero. They have the same size and alignment specifications as instantiations withvalue_count == 1
. They are alwaysDefaultConstructible
, even whenT
is not. —end note]-2- In the case that
, the values of
Nvalue_count == 0begin()
and==end()
are unspecified but they shall be identical. The return value of==
unique valuedata()
is unspecified.-3- The effect of calling
front()
orback()
for a zero-sized array is undefined.-4- Member function
swap()
shall have a noexcept-specification which is equivalent tonoexcept(true)
.
Modify section 23.3.2.9 [array.tuple]
tuple_size<array<T, N...> >::value-1- Return type: integral constant expression.
-2- Value:
Narray<T, N...>::value_counttuple_element<I, array<T, N...> >::type-3- Requires:
I <
. The program is ill-formed ifNarray<T, N...>::value_countI
is out of bounds.-4- Value: The type
T
.template <size_t I, class T, size_t ...N> constexpr T& get(array<T, N...>& a) noexcept;-5- Requires:
I <
. The program is ill-formed ifNarray<T, N...>::value_countI
is out of bounds.-6- Returns: A reference to the
I
th element ofa
, where indexing is zero-based and uses the forward-iteration order (23.3.2.C).template <size_t I, class T, size_t ...N> constexpr T&& get(array<T, N...>&& a) noexcept;-7- Effects: Equivalent to
return std::move(get<I>(a));
template <size_t I, class T, size_t ...N> constexpr const T& get(const array<T, N...>& a) noexcept;-8- Requires:
I <
. The program is ill-formed ifNarray<T, N...>::value_countI
is out of bounds.-9- Returns: A const reference to the
I
th element ofa
, where indexing is zero-based and uses the forward-iteration order (23.3.2.C).
Author's Note: When sizeof...(N) == 1
, get
acts with the expected semantics. When N
is empty, 0
is the only valid value for I
and get
returns the
sole element. Otherwise, the multiple indexes needed to reference an element
are flattened to the index in "row-major" linear traversal order.