Doc. no.: | P0325R4 |
Date: | 2019-07-17 |
Audience: | LWG |
Reply-to: | Zhihao Yuan <zy at miator dot net> |
to_array
from LFTS with updates
Changes since R3
- simplify returns
- mandates and expects
- highlight insertion in wording
- add feature testing macro
Changes since R2
- switch back to
{{ a[
i]... }}
in specification
Introduction
This paper proposes to adopt to_array
from Library Fundamentals TS along with an rvalue-reference overload based on the feedback from the users.
auto lv = to_array("foo");
auto rv = to_array<int>({ 'a', 'b' })
Discussion
In C++17 we introduced deduction guide on std::array
:
auto a = array{ 1, 2, 3 };
But it is impossible to create an array<char, 4>
from array{ "foo" }
because otherwise, one won’t be able to explain why array{ "foo", "bar" }
creates array<char const*, 2>
. So one overload set, despite whether it’s std::array
deduction guide or make_array
from LFTSv2, is not enough to serve the purpose of “creating a std::array
from elements” and “creating a std::array
by copying from a built-in array” at the same time. That is why we introduced to_array
in LFTSv2:
auto a1 = to_array("foo"); // array<char, 4>
auto a2 = array{ "foo" }; // array<char const*, 1>
For the same reason, to_array
should be adopted into the IS.
However, the original version of to_array
only takes an lvalue reference-to-array, and users reported some issues regarding this design. The first issue is that it becomes very hackish to deduce only the array bound from a braced-init-list:
auto x = to_array<int const>({ 2, 4 }); // array<int, 2>
The language treats braced-init-list as a prvalue of an array type when deducing against reference-to-array. The code above adds const
to coin a reference that can bind to such a prvalue, and leaves the const
to be stripped later. Anyhow, the code isn’t doing what it says.
The second issue is that it does not support move-only elements, such as unique_ptr
, no matter how you hack.
So we should add the rvalue-reference overload to fill this hole regarding type system.
Impact on the Standard
The following table illustrates the uses and the corresponding overload resolution after adopting the proposed addition. Given
int a[3] = {};
struct A { int a; double b; };
use |
argument |
overload |
result |
to_array("meow") |
lvalue char const[5] |
lvalue ref |
array<char, 5> |
to_array({1, 2}) |
prvalue int[2] |
rvalue ref |
array<int, 2> |
to_array<long>({1, 2}) |
prvalue long[2] |
rvalue ref |
array<long, 2> |
to_array(a) |
lvalue int[3] |
lvalue ref |
array<int, 3> |
to_array(std::move(a)) |
xvalue int (&&)[3] |
rvalue ref |
array<int, 3> |
to_array<A>({{3, .1}}) |
prvalue A[1] |
rvalue ref |
array<A, 1> |
to_array((int[]){3, 4}) |
C99 compound literal |
see below |
array<int, 2> |
The last entry is a valuable case to study. Compound literals are lvalues in the C standards, but C does not have rvalue objects of compound types anyway. Both Clang and GCC implement C99 compound literals as extensions in C++ and treat them as prvalues. Let’s take a closer look at this feature,
(int[]){3, 4}
It is an array literal formed by a C-style cast notation (like (char)
) for an array type followed by an initializer list. In the same way, we can interpret to_array
as
to_array<int>({3, 4})
a std::array
literal formed by a C++ style cast notation (like static_cast<char>(...)
) for the array element type surrounding an initializer list.
An interesting consequence of this proposal is that we no longer need make_array
:
LFTSv2 |
C++20 |
make_array(1, 2, 3) |
array{ 1, 2, 3 } |
make_array<long>(1, 2, 3) |
to_array<long>({ 1, 2, 3 }) |
In the future, we may have array<long>{ 1, 2, 3 }
to replace this specific use of to_array
, but that will not defeat the primary purpose of to_array
– a facility to create std::array
by copying or moving from a built-in array.
Supplying the bound with to_array<T, N>({ ... })
is more strict compared to array<T, N>{ ... }
, because brace elision in aggregate initialization can sometimes give unexpected outcomes:
using E = complex<double>;
auto a = array<E, 10>{{1, 2}, {3, 4}};
auto b = array<E, 10>{{{1, 2}, {3, 4}}};
auto c = array<E, 10>{{1, 2}};
Implementation
Godbolted: https://godbolt.org/g/GqbygN
Wording
This wording is relative to N4820.
New section 22.3.7.6 [array.creation] (between [array.zero] and [array.tuple], which was 22.3.7.6):
22.3.7.6 Array creation functions [array.creation]
template<class T, size_t N>
constexpr array<remove_cv_t<T>, N> to_array(T (&a)[N]);
Mandates: is_array_v<T>
is false
and is_constructible_v<T, T&>
is true
.
Expects: T
meets the Cpp17CopyConstructible requirements.
Returns: {{ a[0],
..., a[N - 1] }}
.
template<class T, size_t N>
constexpr array<remove_cv_t<T>, N> to_array(T (&&a)[N]);
Mandates: is_array_v<T>
is false
and is_move_constructible_v<T>
is true
.
Expects: T
meets the Cpp17MoveConstructible requirements.
Returns: {{ std::move(a[0]),
..., std::move(a[N - 1]) }}
.
Add those signatures to 22.3.2 [array.syn]:
namespace std {
// 22.3.7, class template array
template<class T, size_t N> struct array;
…
template<class T, size_t N>
void swap(array<T, N>& x, array<T, N>& y) noexcept(noexcept(x.swap(y)));
// 22.3.7.6, Array creation functions
template<class T, size_t N>
constexpr array<remove_cv_t<T>, N> to_array(T (&a)[N]);
template<class T, size_t N>
constexpr array<remove_cv_t<T>, N> to_array(T (&&a)[N]);
…
}
Add the following entry to Table 36 [tab:support.ft]:
Macro name |
Value |
Header(s) |
__cpp_lib_to_array |
xxxxxxL |
<array> |
Acknowledgements
Thank Peter Sommerlad for the inputs on this paper.
References
to_array
from LFTS with updatesChanges since R3
Changes since R2
{{ a[
]... }}
in specificationIntroduction
This paper proposes to adopt
to_array
from Library Fundamentals TS along with an rvalue-reference overload based on the feedback from the users.auto lv = to_array("foo"); // array<char, 4> auto rv = to_array<int>({ 'a', 'b' }) // array<int, 2>
Discussion
In C++17 we introduced deduction guide on
std::array
:auto a = array{ 1, 2, 3 };
But it is impossible to create an
array<char, 4>
fromarray{ "foo" }
because otherwise, one won’t be able to explain whyarray{ "foo", "bar" }
createsarray<char const*, 2>
. So one overload set, despite whether it’sstd::array
deduction guide ormake_array
from LFTSv2, is not enough to serve the purpose of “creating astd::array
from elements” and “creating astd::array
by copying from a built-in array” at the same time. That is why[1] we introducedto_array
in LFTSv2:auto a1 = to_array("foo"); // array<char, 4> auto a2 = array{ "foo" }; // array<char const*, 1>
For the same reason,
to_array
should be adopted into the IS.However, the original version of
to_array
only takes an lvalue reference-to-array, and users reported[2] some issues regarding this design. The first issue is that it becomes very hackish to deduce only the array bound from a braced-init-list:auto x = to_array<int const>({ 2, 4 }); // array<int, 2>
The language treats braced-init-list as a prvalue of an array type when deducing against reference-to-array. The code above adds
const
to coin a reference that can bind to such a prvalue, and leaves theconst
to be stripped later. Anyhow, the code isn’t doing what it says.The second issue is that it does not support move-only elements, such as
unique_ptr
, no matter how you hack.So we should add the rvalue-reference overload to fill this hole regarding type system.
Impact on the Standard
The following table illustrates the uses and the corresponding overload resolution after adopting the proposed addition. Given
int a[3] = {}; struct A { int a; double b; };
to_array("meow")
char const[5]
array<char, 5>
to_array({1, 2})
int[2]
array<int, 2>
to_array<long>({1, 2})
long[2]
array<long, 2>
to_array(a)
int[3]
array<int, 3>
to_array(std::move(a))
int (&&)[3]
array<int, 3>
to_array<A>({{3, .1}})
A[1]
array<A, 1>
to_array((int[]){3, 4})
array<int, 2>
The last entry is a valuable case to study. Compound literals are lvalues in the C standards, but C does not have rvalue objects of compound types anyway. Both Clang and GCC implement C99 compound literals as extensions in C++ and treat them as prvalues. Let’s take a closer look at this feature,
(int[]){3, 4}
It is an array literal formed by a C-style cast notation (like
(char)
) for an array type followed by an initializer list. In the same way, we can interpretto_array
asto_array<int>({3, 4})
a
std::array
literal formed by a C++ style cast notation (likestatic_cast<char>(...)
) for the array element type surrounding an initializer list.An interesting consequence of this proposal is that we no longer need
make_array
:make_array(1, 2, 3)
array{ 1, 2, 3 }
make_array<long>(1, 2, 3)
to_array<long>({ 1, 2, 3 })
In the future, we may have
array<long>{ 1, 2, 3 }
to replace this specific use ofto_array
, but that will not defeat the primary purpose ofto_array
– a facility to createstd::array
by copying or moving from a built-in array.Supplying the bound with
to_array<T, N>({ ... })
is more strict compared toarray<T, N>{ ... }
, because brace elision in aggregate initialization can sometimes give unexpected outcomes:using E = complex<double>; // Nicol's quiz time auto a = array<E, 10>{{1, 2}, {3, 4}}; auto b = array<E, 10>{{{1, 2}, {3, 4}}}; auto c = array<E, 10>{{1, 2}};
Implementation
Godbolted: https://godbolt.org/g/GqbygN
Wording
This wording is relative to N4820.
New section 22.3.7.6 [array.creation] (between [array.zero] and [array.tuple], which was 22.3.7.6):
22.3.7.6 Array creation functions [array.creation]
template<class T, size_t N> constexpr array<remove_cv_t<T>, N> to_array(T (&a)[N]);
template<class T, size_t N> constexpr array<remove_cv_t<T>, N> to_array(T (&&a)[N]);
Add those signatures to 22.3.2 [array.syn]:
Add the following entry to Table 36 [tab:support.ft]:
__cpp_lib_to_array
xxxxxxL
<array>
Acknowledgements
Thank Peter Sommerlad for the inputs on this paper.
References
N4391 make_array, revision 4. http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4391.html ↩︎
LWG 2814
to_array
should take rvalue reference as well. https://cplusplus.github.io/LWG/issue2814 ↩︎