T (&&arr)[N]
is not a forwarding reference, so you shouldn’t usestd::forward
here. Usestd::move
, because you intend to take this lvalue and (unconditionally) turn it into an rvalue; that’s whatstd::move
is for.
You’re totally right, @Arthur, thanks. I messed it up quite badly. So here you are what a (hopefully correct) r-value specialization may look like:
template <std::size_t Idx, typename T, std::size_t N>
constexpr T&& get(T (&&arr)[N]) noexcept
{static_assert(Idx < N, "Index out of bounds");
return std::move(arr)[Idx];
}
@Lénárd’s observation about MSVC raises the question whether the return type should be T&&
or decltype(auto)
. In the latter case, calling get on an r-value C-style array would return an l-value (on MSVC).
Again, I’d stick to what the standard already mandates for std::array
. So, in my opinion, the above specialization should return T&&
.
Moreover, the decltype(auto)
version would instanciate functions with different signatures on different compilers, e.g.:
constexpr int& get<0, int, 3>(int (&&)[3]) noexcept; // MSVC
constexpr int&& get<0, int, 3>(int (&&)[3]) noexcept; // Clang/GCC
I’m not really sure whether this is an issue, since the return type of std::move(arr)[Idx]
itself is not consistent across compilers.
As for the rest of the tuple-like interface for T[N]
, I’ve come up with this:
namespace std
{
template <typename T, size_t N>
struct tuple_size<T[N]>
public integral_constant<size_t, N> { };
:
// I have to add the following explicit specialization. If I don't,
// tuple_size<T const[N]> would match both the following
// specializations:
//
// 1. <U[N]> with U = T const (i.e. the specialization above);
// 2. <U const> with U = T[N] (alredy in the standard).
//
// Resulting in a compiler error.
template <typename T, size_t N>
struct tuple_size<T const[N]> : public tuple_size<T[N]> { };
template <typename T, size_t N>
struct tuple_size<T volatile[N]> : public tuple_size<T[N]> { };
template <typename T, size_t N>
struct tuple_size<T const volatile[N]> : public tuple_size<T[N]> { };
// --
namespace detail
{template <size_t Idx, typename T>
struct array_tuple_element;
template <size_t Idx, typename T, size_t N>
struct array_tuple_element<Idx, T[N]>
{static_assert(Idx < N, "Index out of bounds");
using type = T;
};
}
template <size_t Idx, typename T, size_t N>
struct tuple_element<Idx, T[N]>
public detail::array_tuple_element<Idx, T[N]> { };
:
// -- I have to specify those for the same reason above.
template <size_t Idx, typename T, size_t N>
struct tuple_element<Idx, T const[N]>
public detail::array_tuple_element<Idx, T const[N]> { };
:
template <size_t Idx, typename T, size_t N>
struct tuple_element<Idx, T volatile[N]>
public detail::array_tuple_element<Idx, T volatile[N]> { };
:
template <size_t Idx, typename T, size_t N>
struct tuple_element<Idx, T const volatile[N]>
public detail::array_tuple_element<Idx, T const volatile[N]> { };
: // --
}
As suggested by @Zhihao, it’s true that a tuple-like interface is not needed for structured bindings to C-style arrays. However, it would allow for:
std::apply
(as suggested by @Lénárd);std::make_from_tuple
;std::tuple_cat
(implementations are allowed to support tuple-like objects).Here you are a link to play with:
https://godbolt.org/z/Ts3fn3YT4
(Note: I had to specialize struct std::__is_tuple_like_impl
for T[N]
in order to pass "bye"
to std::tuple_cat
on Clang/GCC).
On Tuesday, March 1st, 2022 at 10:25 AM, Lénárd Szolnoki via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
> > [...] Structured bindings have dedicated
> > rules for C arrays[dcl.struct.bind] (eel.is) and it kicks inearly,
> > ignoring all tuple_* stuff.
>
> As you write, it wouldn't do anything for structured bindings, but it
> would enable C arrays for `std::apply`.
>
Cool! Love to see it.
--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals