C++ Logo

std-proposals

Advanced search

Re: [std-proposals] C-style array specialization for std::get

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Fri, 4 Mar 2022 13:19:12 -0500
On Fri, Mar 4, 2022 at 12:06 PM Paolo Di Giglio via Std-Proposals <
std-proposals_at_[hidden]> 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
not
    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

–Arthur

Received on 2022-03-04 18:19:24