Date: Sat, 30 Nov 2024 22:15:00 +0800
---------- Forwarded message ---------
From: Desmond Gold <desmondbongcawel922_at_[hidden]>
Date: Sat, Nov 30, 2024 at 8:15 PM
Subject: destructurable std::integer_sequence
To: <std-proposals_at_[hidden]>
After P1061R10 <https://wg21.link/P1061R10> has been approved for C++26,
it's now time to let std::integer_sequence be a destructurable type for
improved usability while still remain its niche
auto [...Is] = std::make_index_sequence<N>();
This addition helps simplify metaprogramming implementations that rely on
std::integer_sequence. Most of these implementations declare another helper
function that takes std::integer_sequence that does the actual work. But
with this addition, you don't need to do that.
// before:
template <typename Array, std::size_t... I>
constexpr auto array_to_tuple_impl(const Array& a,
std::index_sequence<I...>)
{
return std::make_tuple(a[I]...);
}
template <typename T, std::size_t N, typename Indx =
std::make_index_sequence<N>>
constexpr auto array_to_tuple(const std::array<T, N>& a)
{
return details::array_to_tuple_impl(a, Indx{});
}
// after:
template <typename T, std::size_t N>
constexpr auto array_to_tuple(const std::array<T, N>& a)
{
auto [...Is] = std::make_index_sequence<N>();
return std::make_tuple(a[Is]...);
}
One of the motivating examples of P1061R10 which requires
std::index_sequence to be destructurable:
template <class P, class Q>
auto dot_product(P p, Q q) {
// no helper function necessary!
auto [...Is] = std::make_index_sequence<std::tuple_size_v<P>>{};
return (... + (std::get<Is>(p) * std::get<Is>(q)));
}
There are two options in making these destructurable sequences possible:
1.) destructured into individual std::integral_constant:
template<typename _Tp, _Tp... _Is>
struct tuple_size<std::integer_sequence<_Tp, _Is...>>
: std::integral_constant<std::size_t, sizeof...(_Is)> { };
template<std::size_t _I, typename _Tp, _Tp... _Is>
struct tuple_element<_I, std::integer_sequence<_Tp, _Is...>>
{ using type = std::integral_constant<_Tp, _Is...[_I]>; };
template<std::size_t _I, typename _Tp, _Tp... _Is>
constexpr std::integral_constant<_Tp, _Is...[_I]>
get(std::integer_sequence<_Tp, _Is...>) noexcept
{ return {}; }
2.) destructured into constant integers:
template<typename _Tp, _Tp... _Is>
struct tuple_size<std::integer_sequence<_Tp, _Is...>>
: std::integral_constant<std::size_t, sizeof...(_Is)> { };
template<std::size_t _I, typename _Tp, _Tp... _Is>
struct tuple_element<_I, std::integer_sequence<_Tp, _Is...>>
{ using type = _Tp; };
template<std::size_t _I, typename _Tp, _Tp... _Is>
constexpr _Tp get(std::integer_sequence<_Tp, _Is...>) noexcept
{ return _Is...[_I]; }
There are notable differences between these options.
- Option 1 decomposes into std::integral_constant of different types. This
allows you to declare like this which is usable in constant-expressions
converted into underlying value type.
auto [...Is] = std::make_index_sequence<N>();
- Option 2 decomposes into integral constants of the same value type. This
requires you to declare structured binding as constexpr (made possible by
P2686R5 <https://wg21.link/P2686R5>).
constexpr auto [...Is] = std::make_index_sequence<N>();
However, there is one problem. What if the integer sequence is empty? I
don't know if P1061R10 permits empty structured binding packs from an
object of zero tuple size. If this isn't currently allowed, then I believe
this is the hole of the proposal which needs a core language fix
(probably?) or worse never add this library feature because of this. Or add
this and make workarounds such as:
template <typename T, std::size_t N>
constexpr auto array_to_tuple(const std::array<T, N>& a)
{
if constexpr (N != 0)
{
auto [...Is] = std::make_index_sequence<N>();
return std::make_tuple(a[Is]...);
}
else
{
return std::make_tuple();
}
}
From: Desmond Gold <desmondbongcawel922_at_[hidden]>
Date: Sat, Nov 30, 2024 at 8:15 PM
Subject: destructurable std::integer_sequence
To: <std-proposals_at_[hidden]>
After P1061R10 <https://wg21.link/P1061R10> has been approved for C++26,
it's now time to let std::integer_sequence be a destructurable type for
improved usability while still remain its niche
auto [...Is] = std::make_index_sequence<N>();
This addition helps simplify metaprogramming implementations that rely on
std::integer_sequence. Most of these implementations declare another helper
function that takes std::integer_sequence that does the actual work. But
with this addition, you don't need to do that.
// before:
template <typename Array, std::size_t... I>
constexpr auto array_to_tuple_impl(const Array& a,
std::index_sequence<I...>)
{
return std::make_tuple(a[I]...);
}
template <typename T, std::size_t N, typename Indx =
std::make_index_sequence<N>>
constexpr auto array_to_tuple(const std::array<T, N>& a)
{
return details::array_to_tuple_impl(a, Indx{});
}
// after:
template <typename T, std::size_t N>
constexpr auto array_to_tuple(const std::array<T, N>& a)
{
auto [...Is] = std::make_index_sequence<N>();
return std::make_tuple(a[Is]...);
}
One of the motivating examples of P1061R10 which requires
std::index_sequence to be destructurable:
template <class P, class Q>
auto dot_product(P p, Q q) {
// no helper function necessary!
auto [...Is] = std::make_index_sequence<std::tuple_size_v<P>>{};
return (... + (std::get<Is>(p) * std::get<Is>(q)));
}
There are two options in making these destructurable sequences possible:
1.) destructured into individual std::integral_constant:
template<typename _Tp, _Tp... _Is>
struct tuple_size<std::integer_sequence<_Tp, _Is...>>
: std::integral_constant<std::size_t, sizeof...(_Is)> { };
template<std::size_t _I, typename _Tp, _Tp... _Is>
struct tuple_element<_I, std::integer_sequence<_Tp, _Is...>>
{ using type = std::integral_constant<_Tp, _Is...[_I]>; };
template<std::size_t _I, typename _Tp, _Tp... _Is>
constexpr std::integral_constant<_Tp, _Is...[_I]>
get(std::integer_sequence<_Tp, _Is...>) noexcept
{ return {}; }
2.) destructured into constant integers:
template<typename _Tp, _Tp... _Is>
struct tuple_size<std::integer_sequence<_Tp, _Is...>>
: std::integral_constant<std::size_t, sizeof...(_Is)> { };
template<std::size_t _I, typename _Tp, _Tp... _Is>
struct tuple_element<_I, std::integer_sequence<_Tp, _Is...>>
{ using type = _Tp; };
template<std::size_t _I, typename _Tp, _Tp... _Is>
constexpr _Tp get(std::integer_sequence<_Tp, _Is...>) noexcept
{ return _Is...[_I]; }
There are notable differences between these options.
- Option 1 decomposes into std::integral_constant of different types. This
allows you to declare like this which is usable in constant-expressions
converted into underlying value type.
auto [...Is] = std::make_index_sequence<N>();
- Option 2 decomposes into integral constants of the same value type. This
requires you to declare structured binding as constexpr (made possible by
P2686R5 <https://wg21.link/P2686R5>).
constexpr auto [...Is] = std::make_index_sequence<N>();
However, there is one problem. What if the integer sequence is empty? I
don't know if P1061R10 permits empty structured binding packs from an
object of zero tuple size. If this isn't currently allowed, then I believe
this is the hole of the proposal which needs a core language fix
(probably?) or worse never add this library feature because of this. Or add
this and make workarounds such as:
template <typename T, std::size_t N>
constexpr auto array_to_tuple(const std::array<T, N>& a)
{
if constexpr (N != 0)
{
auto [...Is] = std::make_index_sequence<N>();
return std::make_tuple(a[Is]...);
}
else
{
return std::make_tuple();
}
}
Received on 2024-11-30 14:16:18