• Array size validity and SFINAE

    From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Wed Feb 25 08:08:25 2026
    From Newsgroup: comp.lang.c++

    #include <iostream>

    struct S { static constexpr int N = -1; };

    template <typename T> void foo(int [T::N])
    {
    std::cout << "array" << std::endl;
    }

    template <typename T> void foo(...)
    {
    std::cout << "..." << std::endl;
    }

    int main()
    {
    foo<int>(0);
    }

    GCC outputs "array", Clang and MSVC output "..."

    The struct definition can be changed to `struct S {};` with the same
    behavior from GCC. I.e. in template contexts GCC does not check the
    validity of the size specified in array parameter declaration: it
    doesn't care whether it is positive, it doesn't even care whether the
    nested name exists. One can guess that this is most likely a consequence
    of the array parameter type adjustment, which makes the size essentially "disappear". However, it still does not necessarily mean that such array parameter declaration should be accepted as valid.

    So, who's right here standard-wise?
    --
    Best regards,
    Andrey
    --- Synchronet 3.21b-Linux NewsLink 1.2
  • From Marcel Mueller@news.5.maazl@spamgourmet.org to comp.lang.c++ on Sun Mar 1 16:55:04 2026
    From Newsgroup: comp.lang.c++

    Am 25.02.26 um 17:08 schrieb Andrey Tarasevich:
    -a #include <iostream>

    -a struct S { static constexpr int N = -1; };

    -a template <typename T> void foo(int [T::N])
    -a {
    -a-a-a std::cout << "array" << std::endl;
    -a }

    -a template <typename T> void foo(...)
    -a {
    -a-a-a std::cout << "..." << std::endl;
    -a }

    -a int main()
    -a {
    -a-a-a foo<int>(0);
    -a }

    GCC outputs "array", Clang and MSVC output "..."

    You are taking an array by value. This might not be the intention and
    might take a slice. Since the array index is unsigned -1 is UB or in
    fact expand to UINT_MAX.

    If you write
    template <typename T> void foo(int (&)[T::N])
    gcc will also output ...

    I am unsure whether the overload resolution should take place before the
    other checks.


    Marcel
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Sun Mar 1 08:23:41 2026
    From Newsgroup: comp.lang.c++

    On Sun 3/1/2026 7:55 AM, Marcel Mueller wrote:
    -a-a #include <iostream>

    -a-a struct S { static constexpr int N = -1; };

    -a-a template <typename T> void foo(int [T::N])
    -a-a {
    -a-a-a-a std::cout << "array" << std::endl;
    -a-a }

    -a-a template <typename T> void foo(...)
    -a-a {
    -a-a-a-a std::cout << "..." << std::endl;
    -a-a }

    -a-a int main()
    -a-a {
    -a-a-a-a foo<int>(0);
    -a-a }

    GCC outputs "array", Clang and MSVC output "..."

    You are taking an array by value.

    There's no such thing as "taking an array by value". Neither in C nor in
    C++.

    This might not be the intention and
    might take a slice.

    ???

    Since the array index is unsigned -1 is UB or in
    fact expand to UINT_MAX.

    Um... My question is intended for people who have "language lawyer"
    level of understanding the language. You are obviously not quite there yet.

    At some point in the future you you might come across the concept
    colloquially known as SFINAE (which is mentioned in the subject of my question). Once you learn what it is (if ever), you will understand what
    this question is about.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Sun Mar 1 16:36:01 2026
    From Newsgroup: comp.lang.c++

    On 3/1/2026 8:23 AM, Andrey Tarasevich wrote:
    On Sun 3/1/2026 7:55 AM, Marcel Mueller wrote:
    -a-a #include <iostream>

    -a-a struct S { static constexpr int N = -1; };

    -a-a template <typename T> void foo(int [T::N])
    -a-a {
    -a-a-a-a std::cout << "array" << std::endl;
    -a-a }

    -a-a template <typename T> void foo(...)
    -a-a {
    -a-a-a-a std::cout << "..." << std::endl;
    -a-a }

    -a-a int main()
    -a-a {
    -a-a-a-a foo<int>(0);
    -a-a }

    GCC outputs "array", Clang and MSVC output "..."

    You are taking an array by value.

    There's no such thing as "taking an array by value". Neither in C nor in C++.

    pseudo-code sorry for any typos... Not your main point, but

    struct foo
    {
    int xxx[10];
    };


    void
    bar(struct foo bar)
    {
    bar.xxx[0] = 123;
    }




    This might not be the intention and might take a slice.

    ???

    Since the array index is unsigned -1 is UB or in fact expand to UINT_MAX.

    Um... My question is intended for people who have "language lawyer"
    level of understanding the language. You are obviously not quite there yet.

    At some point in the future you you might come across the concept colloquially known as SFINAE (which is mentioned in the subject of my question). Once you learn what it is (if ever), you will understand what this question is about.


    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Sun Mar 1 16:45:56 2026
    From Newsgroup: comp.lang.c++

    On Sun 3/1/2026 4:36 PM, Chris M. Thomasson wrote:

    There's no such thing as "taking an array by value". Neither in C nor
    in C++.

    pseudo-code sorry for any typos... Not your main point, but


    With the same success you could've posted an example passing a
    `std::array` by value. Not relevant.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c++ on Mon Mar 2 00:38:52 2026
    From Newsgroup: comp.lang.c++

    Andrey Tarasevich <noone@noone.net> writes:

    #include <iostream>

    struct S { static constexpr int N = -1; };

    template <typename T> void foo(int [T::N])
    {
    std::cout << "array" << std::endl;
    }

    template <typename T> void foo(...)
    {
    std::cout << "..." << std::endl;
    }

    int main()
    {
    foo<int>(0);
    }

    GCC outputs "array", Clang and MSVC output "..."

    The struct definition can be changed to `struct S {};` with the same
    behavior from GCC. I.e. in template contexts GCC does not check the
    validity of the size specified in array parameter declaration: it
    doesn't care whether it is positive, it doesn't even care whether the
    nested name exists. One can guess that this is most likely a
    consequence of the array parameter type adjustment, which makes the
    size essentially "disappear". However, it still does not necessarily
    mean that such array parameter declaration should be accepted as
    valid.

    So, who's right here standard-wise?

    I certainly don't claim to be a C++ language lawyer; at best I
    might agree to be termed a language paralegal. That said, here are
    my thoughts.

    First, the struct S is irrelevant; it has no bearing on the
    question or the observed behavior.

    Second, clang is right, and gcc is wrong. The output under gcc of
    "array" would seem to imply that int::N exists, but it doesn't.

    Third, if we add a statement

    int fred = T::N;

    to the first template, gcc gives an error, whereas clang still
    outputs '...'. My understanding of SFINAE matches what clang
    does, and does not match what gcc does.

    Fourth, if after adding 'int fred = ...' to the first template, we
    change the call in main() to

    foo<S>(0)

    then gcc again outputs 'array', whereas clang outputs '...'.

    Fifth, if after adding 'int fred = ...' to the first template, we
    define a new struct

    struct U { static constexpr unsigned int N = -1; };

    and change the invocation in main() to

    foo<U>(0)

    then both gcc and clang both output 'array'.

    Incidentally, trying to define an analogous pair of overloaded
    functions simply rejects the definition with a negative value for
    the extent of the array parameter. That holds for both gcc and
    clang.

    In every case clang does what I expect, and is consistent with what
    I understand about SFINAE. I can't say the same for gcc. So,
    either my understanding of SFINAE is messed up, or gcc is confused
    about something.
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Bonita Montero@Bonita.Montero@gmail.com to comp.lang.c++ on Mon Mar 2 13:21:20 2026
    From Newsgroup: comp.lang.c++

    Am 01.03.2026 um 16:55 schrieb Marcel Mueller:

    You are taking an array by value. This might not be the intention and
    might take a slice. Since the array index is unsigned -1 is UB or in
    fact expand to UINT_MAX.
    If you write
    -a template <typename T> void foo(int (&)[T::N])
    gcc will also output ...
    I am unsure whether the overload resolution should take place
    before the other checks.

    Or better a C++ span with a fixed size.With that you can have
    bouds-checking wile debugging.

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Mon Mar 2 07:48:41 2026
    From Newsgroup: comp.lang.c++

    On Mon 3/2/2026 12:38 AM, Tim Rentsch wrote:

    First, the struct S is irrelevant; it has no bearing on the
    question or the observed behavior.

    You are right. However, the `foo<int>` call in my code is a remnant of
    an experiment with non-existing nested name. It also illustrates the
    problem.

    Yet my original intent was to use

    foo<S>();

    call.

    Third, if we add a statement

    int fred = T::N;

    to the first template, gcc gives an error, whereas clang still
    outputs '...'.

    Add where exactly? Into the body of the function template? That would immediately take us out of SFINAE territory and make it a hard error in
    cases where `T::N` does not exist. Not surprisingly, any compiler that
    chooses the first overload will subsequently issue an error.

    Fourth, if after adding 'int fred = ...' to the first template, we
    change the call in main() to

    foo<S>(0)

    then gcc again outputs 'array', whereas clang outputs '...'.

    That is another facet of the problem I mentioned in my original
    question, since array declaration with negative array size is supposed
    to be invalid.

    Not sure what importance you assign to that "after adding..." part,
    since adding 'int fred = ...' to the first template is completely beside
    the point in this case.

    Fifth, if after adding 'int fred = ...' to the first template, we
    define a new struct

    struct U { static constexpr unsigned int N = -1; };

    and change the invocation in main() to

    foo<U>(0)

    then both gcc and clang both output 'array'.

    This is expected, of course. Again, adding 'int fred = ...' to the first template has no bearing on it.

    Incidentally, trying to define an analogous pair of overloaded
    functions simply rejects the definition with a negative value for
    the extent of the array parameter. That holds for both gcc and
    clang.

    Yes, in ordinary functions, as opposed to function templates, GCC does
    not ignore any issues with array sizes in parameter declarations, which
    is the reason I specifically mentioned that the issue is only visible in template context.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c++ on Mon Mar 2 21:34:23 2026
    From Newsgroup: comp.lang.c++

    Andrey Tarasevich <noone@noone.net> writes:

    On Mon 3/2/2026 12:38 AM, Tim Rentsch wrote:

    First, the struct S is irrelevant; it has no bearing on the
    question or the observed behavior.

    You are right. However, the `foo<int>` call in my code is a remnant of
    an experiment with non-existing nested name. It also illustrates the problem.

    Yet my original intent was to use

    foo<S>();

    call.

    Third, if we add a statement

    int fred = T::N;

    to the first template, gcc gives an error, whereas clang still
    outputs '...'.

    Add where exactly? Into the body of the function template?

    Yes, just before the std::cout line.

    That would
    immediately take us out of SFINAE territory and make it a hard error
    in cases where `T::N` does not exist.

    Oh. I still think of that as SFINAE, since clang does the right
    thing.

    Not surprisingly, any compiler
    that chooses the first overload will subsequently issue an error.

    Right.

    Fourth, if after adding 'int fred = ...' to the first template, we
    change the call in main() to

    foo<S>(0)

    then gcc again outputs 'array', whereas clang outputs '...'.

    That is another facet of the problem I mentioned in my original
    question, since array declaration with negative array size is supposed
    to be invalid.

    Not sure what importance you assign to that "after adding..." part,
    since adding 'int fred = ...' to the first template is completely
    beside the point in this case.

    It's a way of showing gcc is confused, since there is another
    template that doesn't have an error, which clang correctly
    recognizes.

    Fifth, if after adding 'int fred = ...' to the first template, we
    define a new struct

    struct U { static constexpr unsigned int N = -1; };

    and change the invocation in main() to

    foo<U>(0)

    then both gcc and clang both output 'array'.

    This is expected, of course. Again, adding 'int fred = ...' to the
    first template has no bearing on it.

    Yes, expected. The point of having 'int fred = ...' is to
    emphasize the distinction between gcc and clang considering
    the pair of invocations foo<S>(0) and foo<U>(0).

    Incidentally, trying to define an analogous pair of overloaded
    functions simply rejects the definition with a negative value for
    the extent of the array parameter. That holds for both gcc and
    clang.

    Yes, in ordinary functions, as opposed to function templates, GCC does
    not ignore any issues with array sizes in parameter declarations,
    which is the reason I specifically mentioned that the issue is only
    visible in template context.

    I didn't really think any of my reporting would surprise you. I
    included everything in the interest of completeness and to
    illustrate my thought processes.

    After seeing your reply I tried one more test, changing the first
    template to

    template <typename T> void foo(int (*p)[T::N])
    {
    std::cout << "array of size " << sizeof *p << std::endl;
    }

    in which case the invocation

    foo<S>(0);

    produces '...' under both gcc and clang.
    --- Synchronet 3.21d-Linux NewsLink 1.2