Date: Sat, 23 Aug 2025 22:23:56 +0000
On Sat, Aug 23, 2025 at 10:43:06AM -0400, Arthur O'Dwyer wrote:
> On Fri, Aug 22, 2025 at 4:04 PM Levo D via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> >
> > If we look at the following program, we'll notice several problems.
> > Comments will explicitly explain them.
> > The two biggest issues are
> > 1) no easy way to convert a large array into a smaller array
> > 2) Compilers don't need to error when the size is known and a
> > literal index is outside of it
> >
> > #include <cstddef>
> >
> > void test16(char (&arr)[16]) { arr[15] = 0x12; }
> > void test32(char (&arr)[32]) { arr[31] = 0x34; }
> >
> > // Sanitizers won't catch this if you change 257 to 255
> > void test256(char (&arr)[256]) { arr[257] = 0x56; }
> >
> > template <size_t N> void testN(char (&arr)[N]) {
> > //test16(arr); // error because size is not exact
> > test32(arr); // exact, compiles
> > // Typecasting is bad, as we know
> > test16(reinterpret_cast<char (&)[16]>(arr)); // compiles
> > test256(reinterpret_cast<char (&)[256]>(arr)); // compiles and
> > will overwrite memory
> > }
> >
> > int main() {
> > char buf[32]{};
> > testN(buf); // two problems, 32 is smaller than 256 and
> > // test256 writes to 257, which is clearly out of range
> > // the below doesn't cause a warning (or error) in some compilers
> > buf[-1] = 0x78;
> > // I much rather the previous line be written as
> > *(buf-1) = 0x78;
> > }
> >
>
> You can fix one of those issues by switching away from "reference to array"
> and toward "std::span":
> https://godbolt.org/z/cWjj3sz3n
>
> void test16(std::span<char, 16> arr) { arr[15] = 0x12; }
> void test32(std::span<char, 32> arr) { arr[31] = 0x34; }
> template <size_t N> void testN(char (&arr)[N]) {
> //test16(arr); // error because size is not exact
> test32(arr); // exact, compiles
> // Typecasting is bad, as we know
> test16(std::span(arr).template first<16>()); // compiles
> test256(std::span(arr).template first<256>()); // NO LONGER compiles
> }
>
> However, this is ugly — it requires ".template" — and it doesn't scale or
> compose — you can't replace `testN` with
> template<size_t N> void testN(std::span<char, N> arr)
> because the `N` will not be deducible in a call like `testN(buf)`.
>
> Also, no mainstream compiler today seems to be aware of `span` as a special
> case in their bounds-checking code: every compiler will warn about
> void test256(char (&arr)[256]) { arr[257] = 0x56; } // warning
> but no compiler will warn about
> void test256(std::span<char, 256> arr) { arr[257] = 0x56; } // no
> warning
>
> So indeed `span` isn't a magic bullet for the bounds-checking problem in
> C++. But the problem has still been *reasonably* well solved for half a
> century: all you have to do is pass (pointer, length) or (first, last)
> rather than just (pointer). Don't try to pass around references to arrays;
> as you've noticed, that doesn't really work because most ranges are not
> *precisely* arrays of a given length. But you can write things like:
>
> void test16(char *p, int n) {
> assert(n >= 16);
> p[15] = 0x12;
> }
> void testN(char *p, int n) {
> test16(arr, n);
> ~~~~
> }
>
> and I'd expect a static analyzer or linter to be able to deal with that
> code pretty well.
>
>
> By having 'arr[257]' and 'buf[-1]' become an error, obvious mistakes will
> > be caught immediately.
> >
>
> I suggest you file a bug with the compiler vendor if you find any compilers
> where
> void f(char (&arr)[256]) { arr[257] = 1; }
> doesn't give a warning with -Wall -Wextra. (That means file one against
> GCC, because they warn only if you also pass -O2, and they emit the warning
> as -Warray-bounds, which is explicitly *turned off* in most
> industry projects I've seen, due to its high false-positive rate!)
>
>
> To allow testN to be implemented without casting, I suggest a syntax
>
>
> How about a simple wrapper, such as:
>
> // https://godbolt.org/z/z8GKfhsf4
>
> template<class T, size_t N>
> struct APO {
> T *a_;
> template<size_t M> using Ref = T(&)[M];
> template<size_t M> requires (M <= N)
> operator Ref<M>() const {
> return *reinterpret_cast<T(*)[M]>(a_);
> }
> };
> template<class T, size_t N>
> auto anyPrefixOf(T (&a)[N]) {
> return APO<T, N>{a};
> }
>
> template<size_t N>
> void testN(char (&arr)[N]) {
> test16(anyPrefixOf(arr)); // OK
> test32(anyPrefixOf(arr)); // OK
> test256(anyPrefixOf(arr)); // Error
> }
>
> HTH,
> Arthur
At the start, it seemed like you were for this proposal, but now it seems like you may be neutral? I'm not exactly sure how a proposal goes and how many people should say they like it before it has a chance of making it through.
> Also, no mainstream compiler today seems to be aware of `span` ... So indeed `span` isn't a magic bullet
Yes, I was told I should be thorough and show that a library/class won't be enough.
> How about a simple wrapper, such as:
It's not the same as built-in; I wrap at the moment. I would want a solution allowing "test16(arr)" without anything in between
> On Fri, Aug 22, 2025 at 4:04 PM Levo D via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> >
> > If we look at the following program, we'll notice several problems.
> > Comments will explicitly explain them.
> > The two biggest issues are
> > 1) no easy way to convert a large array into a smaller array
> > 2) Compilers don't need to error when the size is known and a
> > literal index is outside of it
> >
> > #include <cstddef>
> >
> > void test16(char (&arr)[16]) { arr[15] = 0x12; }
> > void test32(char (&arr)[32]) { arr[31] = 0x34; }
> >
> > // Sanitizers won't catch this if you change 257 to 255
> > void test256(char (&arr)[256]) { arr[257] = 0x56; }
> >
> > template <size_t N> void testN(char (&arr)[N]) {
> > //test16(arr); // error because size is not exact
> > test32(arr); // exact, compiles
> > // Typecasting is bad, as we know
> > test16(reinterpret_cast<char (&)[16]>(arr)); // compiles
> > test256(reinterpret_cast<char (&)[256]>(arr)); // compiles and
> > will overwrite memory
> > }
> >
> > int main() {
> > char buf[32]{};
> > testN(buf); // two problems, 32 is smaller than 256 and
> > // test256 writes to 257, which is clearly out of range
> > // the below doesn't cause a warning (or error) in some compilers
> > buf[-1] = 0x78;
> > // I much rather the previous line be written as
> > *(buf-1) = 0x78;
> > }
> >
>
> You can fix one of those issues by switching away from "reference to array"
> and toward "std::span":
> https://godbolt.org/z/cWjj3sz3n
>
> void test16(std::span<char, 16> arr) { arr[15] = 0x12; }
> void test32(std::span<char, 32> arr) { arr[31] = 0x34; }
> template <size_t N> void testN(char (&arr)[N]) {
> //test16(arr); // error because size is not exact
> test32(arr); // exact, compiles
> // Typecasting is bad, as we know
> test16(std::span(arr).template first<16>()); // compiles
> test256(std::span(arr).template first<256>()); // NO LONGER compiles
> }
>
> However, this is ugly — it requires ".template" — and it doesn't scale or
> compose — you can't replace `testN` with
> template<size_t N> void testN(std::span<char, N> arr)
> because the `N` will not be deducible in a call like `testN(buf)`.
>
> Also, no mainstream compiler today seems to be aware of `span` as a special
> case in their bounds-checking code: every compiler will warn about
> void test256(char (&arr)[256]) { arr[257] = 0x56; } // warning
> but no compiler will warn about
> void test256(std::span<char, 256> arr) { arr[257] = 0x56; } // no
> warning
>
> So indeed `span` isn't a magic bullet for the bounds-checking problem in
> C++. But the problem has still been *reasonably* well solved for half a
> century: all you have to do is pass (pointer, length) or (first, last)
> rather than just (pointer). Don't try to pass around references to arrays;
> as you've noticed, that doesn't really work because most ranges are not
> *precisely* arrays of a given length. But you can write things like:
>
> void test16(char *p, int n) {
> assert(n >= 16);
> p[15] = 0x12;
> }
> void testN(char *p, int n) {
> test16(arr, n);
> ~~~~
> }
>
> and I'd expect a static analyzer or linter to be able to deal with that
> code pretty well.
>
>
> By having 'arr[257]' and 'buf[-1]' become an error, obvious mistakes will
> > be caught immediately.
> >
>
> I suggest you file a bug with the compiler vendor if you find any compilers
> where
> void f(char (&arr)[256]) { arr[257] = 1; }
> doesn't give a warning with -Wall -Wextra. (That means file one against
> GCC, because they warn only if you also pass -O2, and they emit the warning
> as -Warray-bounds, which is explicitly *turned off* in most
> industry projects I've seen, due to its high false-positive rate!)
>
>
> To allow testN to be implemented without casting, I suggest a syntax
>
>
> How about a simple wrapper, such as:
>
> // https://godbolt.org/z/z8GKfhsf4
>
> template<class T, size_t N>
> struct APO {
> T *a_;
> template<size_t M> using Ref = T(&)[M];
> template<size_t M> requires (M <= N)
> operator Ref<M>() const {
> return *reinterpret_cast<T(*)[M]>(a_);
> }
> };
> template<class T, size_t N>
> auto anyPrefixOf(T (&a)[N]) {
> return APO<T, N>{a};
> }
>
> template<size_t N>
> void testN(char (&arr)[N]) {
> test16(anyPrefixOf(arr)); // OK
> test32(anyPrefixOf(arr)); // OK
> test256(anyPrefixOf(arr)); // Error
> }
>
> HTH,
> Arthur
At the start, it seemed like you were for this proposal, but now it seems like you may be neutral? I'm not exactly sure how a proposal goes and how many people should say they like it before it has a chance of making it through.
> Also, no mainstream compiler today seems to be aware of `span` ... So indeed `span` isn't a magic bullet
Yes, I was told I should be thorough and show that a library/class won't be enough.
> How about a simple wrapper, such as:
It's not the same as built-in; I wrap at the moment. I would want a solution allowing "test16(arr)" without anything in between
Received on 2025-08-23 22:23:59