Date: Tue, 4 Mar 2025 12:44:47 -0500
On Tue, Mar 4, 2025 at 10:01 AM Avi Kivity via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> C++ has a literal range type: std::initializer_list. However, its elements
> are const and so initializing a container from the elements involves a copy.
> I propose std::ranges::literal<T>, constructed from
> std::convertible_to<T>....
>
Nit: This really has nothing to do with `ranges`.
The state of the art is well covered by Jason Turner's C++Now talk from 7
years ago:
https://www.youtube.com/watch?v=sSlmmZMFsXQ
He covers some alternatives such as
template<class... Elts> explicit vector(std::in_place_t, Elts&&...
elts); // informally proposed by Simon Brand
<https://wg21.tartanllama.xyz/initializer_list.html>
and
template<size_t N> vector(std::array<value_type, N>&& elts);
Your proposal,
auto v = std::vector<big>(std::from_range,
std::ranges::literal<big>(big(7), big(3), big(9)));
looks to do the same thing as, but have a heavier-weight spelling than,
Simon Brand's simpler
auto v = std::vector<big>(std::in_place, std::array{big(7), big(3),
big(9)});
Jason suggests <https://www.youtube.com/watch?v=sSlmmZMFsXQ&t=4022s> adding
"some kind of movable_initializer_list". This has been proposed by Rodrigo
Castro Campos in 2008 (mutable_initializer_list<T>):
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2719.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2801.pdf
and again by David Krauss in 2014–2015 (own_initializer_list<T>):
https://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4166.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0065r0.pdf
> If all constructor parameters are rvalues, then the range produces rvalues
> too.
>
What if some constructor arguments aren't rvalues? It'd have to be
ill-formed then, right?
At any rate, you can't have the return type of
`ranges::literal<T>::iterator::operator*()` *dynamically depend* on how the
`ranges::literal<T>` object was constructed.
One might say, "Why not just have the compiler turn {rvalue, rvalue,
rvalue} into a movable_initializer_list instead of an initializer_list,
automatically?"
But the problem there is that *the vast majority* of initializer-lists
consist of prvalues! Consider:
std::vector<int> v = {1, 2, 3};
It would be a bad thing if this line constructed a
movable_initializer_list<int> on the stack, permitted the vector to
move-out-of its elements, and then had to clean up the moved-from remnants.
What we *want* is for this line to construct an initializer_list<int> to an
immutable array {1,2,3} in static storage (.rodata). And in fact that's
what we get, since P2752
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2752r3.html> was
DR'ed.
Here's GCC's codegen. Clang trunk still refuses to implement P2752.
https://godbolt.org/z/Wj575fz4z
So there's a tension here: We would like the compiler to "just give me move
semantics" when necessary (e.g. with an initializer-list of unique_ptrs, or
an initializer-list of runtime-valued std::strings), but at the same time
we'd like the compiler to give us a nice read-only view over a static data
array whenever that would be *better*. That decision certainly isn't
something that can be made by any paper-standard-friendly heuristic; it
must be left to the implementation.
Executive summary: This is a real, well-known, problem; but I think your
suggested solution of just adding something-like-movable_initializer_list
(and leaving it up to the programmer to decide when to manually apply that
heavyweight syntax) is insufficient. A suitable proposal in this area
should be able to compile
std::vector<std::unique_ptr<int>> v = { std::make_unique<int>(1),
std::make_unique<int>(2) };
right out of the box. If we aren't enabling people to write *that*, then we
aren't really helping them.
People can already write: https://godbolt.org/z/4WT1YGxeb
std::vector<std::unique_ptr<int>> w(
std::from_range,
std::array{
std::make_unique<int>(1),
std::make_unique<int>(2),
} | std::views::as_rvalue
);
so, anything that requires this general level of ceremony around it isn't
worth the bother of standardizing. Simply replacing `std::array{...} |
rv::as_rvalue` with `std::ranges::literal{...}` doesn't rise to the
occasion.
–Arthur
std-proposals_at_[hidden]> wrote:
> C++ has a literal range type: std::initializer_list. However, its elements
> are const and so initializing a container from the elements involves a copy.
> I propose std::ranges::literal<T>, constructed from
> std::convertible_to<T>....
>
Nit: This really has nothing to do with `ranges`.
The state of the art is well covered by Jason Turner's C++Now talk from 7
years ago:
https://www.youtube.com/watch?v=sSlmmZMFsXQ
He covers some alternatives such as
template<class... Elts> explicit vector(std::in_place_t, Elts&&...
elts); // informally proposed by Simon Brand
<https://wg21.tartanllama.xyz/initializer_list.html>
and
template<size_t N> vector(std::array<value_type, N>&& elts);
Your proposal,
auto v = std::vector<big>(std::from_range,
std::ranges::literal<big>(big(7), big(3), big(9)));
looks to do the same thing as, but have a heavier-weight spelling than,
Simon Brand's simpler
auto v = std::vector<big>(std::in_place, std::array{big(7), big(3),
big(9)});
Jason suggests <https://www.youtube.com/watch?v=sSlmmZMFsXQ&t=4022s> adding
"some kind of movable_initializer_list". This has been proposed by Rodrigo
Castro Campos in 2008 (mutable_initializer_list<T>):
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2719.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2801.pdf
and again by David Krauss in 2014–2015 (own_initializer_list<T>):
https://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4166.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0065r0.pdf
> If all constructor parameters are rvalues, then the range produces rvalues
> too.
>
What if some constructor arguments aren't rvalues? It'd have to be
ill-formed then, right?
At any rate, you can't have the return type of
`ranges::literal<T>::iterator::operator*()` *dynamically depend* on how the
`ranges::literal<T>` object was constructed.
One might say, "Why not just have the compiler turn {rvalue, rvalue,
rvalue} into a movable_initializer_list instead of an initializer_list,
automatically?"
But the problem there is that *the vast majority* of initializer-lists
consist of prvalues! Consider:
std::vector<int> v = {1, 2, 3};
It would be a bad thing if this line constructed a
movable_initializer_list<int> on the stack, permitted the vector to
move-out-of its elements, and then had to clean up the moved-from remnants.
What we *want* is for this line to construct an initializer_list<int> to an
immutable array {1,2,3} in static storage (.rodata). And in fact that's
what we get, since P2752
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2752r3.html> was
DR'ed.
Here's GCC's codegen. Clang trunk still refuses to implement P2752.
https://godbolt.org/z/Wj575fz4z
So there's a tension here: We would like the compiler to "just give me move
semantics" when necessary (e.g. with an initializer-list of unique_ptrs, or
an initializer-list of runtime-valued std::strings), but at the same time
we'd like the compiler to give us a nice read-only view over a static data
array whenever that would be *better*. That decision certainly isn't
something that can be made by any paper-standard-friendly heuristic; it
must be left to the implementation.
Executive summary: This is a real, well-known, problem; but I think your
suggested solution of just adding something-like-movable_initializer_list
(and leaving it up to the programmer to decide when to manually apply that
heavyweight syntax) is insufficient. A suitable proposal in this area
should be able to compile
std::vector<std::unique_ptr<int>> v = { std::make_unique<int>(1),
std::make_unique<int>(2) };
right out of the box. If we aren't enabling people to write *that*, then we
aren't really helping them.
People can already write: https://godbolt.org/z/4WT1YGxeb
std::vector<std::unique_ptr<int>> w(
std::from_range,
std::array{
std::make_unique<int>(1),
std::make_unique<int>(2),
} | std::views::as_rvalue
);
so, anything that requires this general level of ceremony around it isn't
worth the bother of standardizing. Simply replacing `std::array{...} |
rv::as_rvalue` with `std::ranges::literal{...}` doesn't rise to the
occasion.
–Arthur
Received on 2025-03-04 17:45:02