On Fri, Mar 4, 2022 at 12:06 PM Paolo Di Giglio via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
> 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];
> }

Because of everything you said about MSVC, I suggest that your proposal (and vendors' implementations too) should actually say
    return std::move(arr[Idx]);  // correct in practice
    return std::move(arr)[Idx];  // pedantically correct but fails on MSVC
The effect is the same, on everything-but-MSVC; and on MSVC the former is preferable. (AFAIK, both are equally correct as far as the paper standard is concerned.)

> 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> { };
> struct tuple_size<T const[N]> : public tuple_size<T[N]> { };

In your actual proposal, please don't overspecify that tuple_size<X> must inherit from tuple_size<Y>; that puts unnecessary and inefficient constraints on the vendor.
However, I think you could actually get away with only this C++20ism instead:
    template<class T, size_t N>
      requires (!is_same_v<T, remove_cv_t<T>>)
    struct tuple_size<T[N]> : public integral_constant<size_t, N> {};
This says that the specialization should be used only for non-const arrays; for const arrays, we'll use the existing <const T> specialization, which will strip off the const and delegate back to this one. (And put a "drafting note" to explain this logic.)

>     template <size_t Idx, typename T>
>     struct detail::array_tuple_element;

I don't understand why you need this layer of indirection at all, even before applying the trick above.
But after applying the trick, it's just
    template<size_t Idx, class T, size_t N>
      requires (!is_same_v<T, remove_cv_t<T>>)
    struct tuple_element<Idx, T[N]> {
      static_assert(Idx < N);
      using type = T;

> 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).
> [...] (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).

Hmm, that last example is mildly scary. String literals in C++ are arrays, which means that if arrays are tuple-like then we'll be able to do
    std::tuple<char, char, char, char, char, char> t = std::tuple_cat("abc", "d");
    assert(t == std::make_tuple('a', 'b', 'c', '\0', 'd', '\0'));
and even
    auto t = std::make_tuple("hello"s, "world"s);
    auto u = std::tuple_cat(t, "in bed"s);
    assert(u == std::make_tuple("hello"s, "world"s, 'i', 'n', ' ', 'b', 'e', 'd', '\0'));
However, given that we can already tuple_cat two std::arrays (producing a std::tuple of their elements, rather than a larger std::array), I think this is just the price of freedom. Consistency between C arrays and std::arrays seems like a big benefit, and if the only downside is one more snippet of cursed code for C++ Twitter to munch on, I think that's a good tradeoff.

Also, and slightly scarier, but still not too bad:
    auto a = std::make_tuple("hello");
    assert(std::tuple_size_v<decltype(a)> == 1);  // OK

    auto b = std::make_from_tuple("world");
    assert(std::tuple_size_v<decltype(b)> == 6);  // cursed