Bug 114479 - [14 Regression] std::is_array_v<int[0]> changed from false to true in GCC 14
Summary: [14 Regression] std::is_array_v<int[0]> changed from false to true in GCC 14
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 14.0
: P1 normal
Target Milestone: 14.0
Assignee: Marek Polacek
URL:
Keywords: wrong-code
Depends on:
Blocks:
 
Reported: 2024-03-26 11:16 UTC by Nikolas Klauser
Modified: 2024-04-02 18:35 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2024-03-26 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Nikolas Klauser 2024-03-26 11:16:56 UTC
```
#include <type_traits>

static_assert(!std::is_array_v<int[0]>);
```
works with GCC 13, but GCC 14 fails to compile. This is most likely caused by the use of the new `__is_array` builtin. It probably has the same bug as Clang (https://github.com/llvm/llvm-project/issues/54705).
Comment 1 Jonathan Wakely 2024-03-26 11:42:43 UTC
Ha, this again. I first observed this in Feb 2022, before GCC's new behaviour. The MSVC behaviour was because their compiler intercepts the std::is_array trait and replaces it with a call to their __is_array intrinsic, which does exactly what clang and gcc's __is_array does.

GCC should fix it too.
Comment 2 Marek Polacek 2024-03-29 15:56:35 UTC
I think the patch is simply

--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12439,7 +12439,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
       return CP_AGGREGATE_TYPE_P (type1);
 
     case CPTK_IS_ARRAY:
-      return type_code1 == ARRAY_TYPE;
+      return (type_code1 == ARRAY_TYPE
+         /* ??? We don't want to report T[0] as being an array type.  */
+         && !(TYPE_SIZE (type1) && integer_zerop (TYPE_SIZE (type1))));
 
     case CPTK_IS_ASSIGNABLE:
       return is_xible (MODIFY_EXPR, type1, type2);

but are we *sure* that we don't want to treat int[0] as an array type?  It's not clear to me that https://github.com/llvm/llvm-project/pull/86652 reached a consensus.
Comment 3 Jonathan Wakely 2024-03-31 10:05:42 UTC
[dcl.array] says that for T[N] the value "N specifies the array bound, i.e., the
number of elements in the array; N shall be greater than zero."

So T[0] is not a valid array type. And std::is_array<T[0]> has never been true for any traditional implementation based on partial specialization of class templates, only when switching to an intrinsic __is_array that fails to accurately give the same behaviour as the std::is_array trait.

Since the purpose of the __is_array intrinsic is to optimize the std::is_array trait, it should have the same behaviour. For the optimization to change the behaviour of the trait seems like the tail wagging the dog.
Comment 4 Marek Polacek 2024-04-01 14:28:56 UTC
Thanks.  I'll go ahead and submit my patch.
Comment 5 Arthur O'Dwyer 2024-04-01 15:25:04 UTC
IIUC, the logic here goes as follows:

(1) Everyone's compiler extension currently makes this assertion fail, not succeed:

  #include <type_traits>
  template<class T> struct is_array : std::false_type {};
  template<class T> struct is_array<T[]> : std::true_type {};
  template<class T, std::size_t N> struct is_array<T[N]> : std::true_type {};
  static_assert(is_array<int[0]>::value, "this assert fails");

(2) Everyone's library is expected to implement `std::is_array` as the moral equivalent of the partial specializations in (1). No reasonable library would ever do anything else.

(3) Therefore, everyone's library will claim that int[0] is not an array type:
  static_assert(std::is_array<int[0]>::value, "this assert fails");

(4) The __is_array builtin is provided specifically to speed up std::is_array (and for no other reason). Therefore, it should give the same answer as (3). Therefore, __is_array(int[0]) should also report false:
  static_assert(__is_array(int[0]), "this assert fails");

This logic doesn't depend on any abstract reasoning about whether int[0] is really "an array type" (I think it certainly *is* an array type, FWIW); it simply depends on observing the extension's behavior in (1) -- and then *not* claiming that that's a bug we need to fix. If the extension's behavior is "correct" (i.e. we're not going to change it), then the behavior of __is_array(T[0]) falls naturally out of that.

Personally, if I were designing the extension today, I would certainly make T[0] match the partial specialization for T[N] (with N=0). This seems like it would match users' expectations, and it doesn't seem to break any code that wasn't already trying to use the extension, except for pathological party tricks like being able to obfuscate the condition (N >= 1) as (std::is_array_v<int[N]>). However, *if* the extension's behavior (1) is set in stone, *then* conclusion (4) follows inexorably.

And since both (1) and (4) are core-language compiler issues, libstdc++ isn't involved here: it's simply a bug for GCC to provide partial-specialization-matching behavior as in (1) without also providing __is_array behavior as in (4).

HOWEVER, here's a big question which I believe Aaron Ballman also raised on llvm-project#54705: If it's not an array type, then where do we get off calling it a compound type ( https://eel.is/c++draft/basic#compound-1 ), a literal type ( https://eel.is/c++draft/basic#types.general-10 ), an aggregate ( https://eel.is/c++draft/dcl.init.aggr#1 ), etc?

It certainly would be *simpler* -- if a big upheaval -- for GCC to provide neither (1) nor (4), i.e. change the specialization-matching behavior to match __is_array rather than the other way around. Then all the traits would give consistent answers: int[0] would be a compound type, a literal type, and an aggregate *because* it was an array type.
Comment 6 GCC Commits 2024-04-02 18:34:59 UTC
The trunk branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:2f2924078ce51c2a0da3ad8f958f2d1de533969a

commit r14-9759-g2f2924078ce51c2a0da3ad8f958f2d1de533969a
Author: Marek Polacek <polacek@redhat.com>
Date:   Mon Apr 1 12:55:46 2024 -0400

    c++: make __is_array return false for T[0] [PR114479]
    
    When we switched to using the __is_array built-in trait to implement
    std::is_array in r14-6623-g7fd9c349e45534, we started saying that
    T[0] is an array.  There are various opinions as to whether that is
    the best answer, but it seems prudent to keep the GCC 13 result.
    
            PR c++/114479
    
    gcc/cp/ChangeLog:
    
            * semantics.cc (trait_expr_value) <case CPTK_IS_ARRAY>: Return false
            for zero-sized arrays.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/ext/is_array.C: Extend.
Comment 7 Marek Polacek 2024-04-02 18:35:30 UTC
Fixed.