[libcxx-commits] [libcxx] [libc++] Fix constexpr initialization of std::array<T, 0> (PR #74667)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Dec 14 06:59:59 PST 2023


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/74667

>From 1f37bb695a7ccc293d4793ff560cd45425c70960 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 6 Dec 2023 17:33:03 -0500
Subject: [PATCH 01/10] [libc++] Fix constexpr initialization of std::array<T,
 0>

This patch fixes constexpr default initialization of empty arrays
and improves the tests accordingly.

Fixes #74375
---
 libcxx/include/array                          |   3 +-
 .../array/array.cons/initialization.pass.cpp  |  25 +++-
 .../array/size_and_alignment.compile.pass.cpp | 130 ++++++++++++++++++
 .../array/size_and_alignment.pass.cpp         |  78 -----------
 4 files changed, 153 insertions(+), 83 deletions(-)
 create mode 100644 libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
 delete mode 100644 libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp

diff --git a/libcxx/include/array b/libcxx/include/array
index 127092f6bca9bd..334fa747339ba7 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -280,7 +280,8 @@ struct _LIBCPP_TEMPLATE_VIS array<_Tp, 0>
     typedef std::reverse_iterator<iterator>       reverse_iterator;
     typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
 
-    typedef __conditional_t<is_const<_Tp>::value, const char, char> _CharType;
+    struct _EmptyAggregate { };
+    typedef __conditional_t<is_const<_Tp>::value, const _EmptyAggregate, _EmptyAggregate> _CharType;
 
     struct  _ArrayInStructT { _Tp __data_[1]; };
     _ALIGNAS_TYPE(_ArrayInStructT) _CharType __elems_[sizeof(_ArrayInStructT)];
diff --git a/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp b/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
index 9153106b384fc0..bdc79e22013f81 100644
--- a/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
@@ -19,11 +19,9 @@ struct NoDefault {
 };
 
 // Test default initialization
-// This one isn't constexpr because omitting to initialize fundamental types
-// isn't valid in a constexpr context.
 struct test_default_initialization {
     template <typename T>
-    void operator()() const
+    TEST_CONSTEXPR_CXX14 void operator()() const
     {
         std::array<T, 0> a0; (void)a0;
         std::array<T, 1> a1; (void)a1;
@@ -34,6 +32,20 @@ struct test_default_initialization {
     }
 };
 
+// Additional tests for constexpr default initialization of empty arrays since
+// it seems that making a variable constexpr and merely having a non-constexpr
+// variable in a constexpr function behave differently.
+//
+// Reproducer for https://github.com/llvm/llvm-project/issues/74375
+struct test_default_initialization_74375 {
+    template <typename T>
+    TEST_CONSTEXPR_CXX14 void operator()() const
+    {
+        constexpr std::array<T, 0> a0; (void)a0;
+        constexpr std::array<NoDefault, 0> nodefault; (void)nodefault;
+    }
+};
+
 struct test_nondefault_initialization {
     template <typename T>
     TEST_CONSTEXPR_CXX14 void operator()() const
@@ -177,10 +189,15 @@ TEST_CONSTEXPR_CXX14 bool with_all_types()
 int main(int, char**)
 {
     with_all_types<test_nondefault_initialization>();
-    with_all_types<test_default_initialization>(); // not constexpr
+    with_all_types<test_default_initialization>();
+    with_all_types<test_default_initialization_74375>();
     test_initializer_list();
 #if TEST_STD_VER >= 14
     static_assert(with_all_types<test_nondefault_initialization>(), "");
+#if TEST_STD_VER >= 20
+    static_assert(with_all_types<test_default_initialization>(), "");
+#endif
+    static_assert(with_all_types<test_default_initialization_74375>(), "");
     static_assert(test_initializer_list(), "");
 #endif
 
diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
new file mode 100644
index 00000000000000..66069715103fd7
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
@@ -0,0 +1,130 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <array>
+
+// template <class T, size_t N>
+// struct array
+
+// Make sure std::array<T, N> has the correct object size and alignment.
+// This test is mostly meant to catch subtle ABI-breaking regressions.
+
+// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems.
+#if defined(_AIX)
+#  pragma clang diagnostic ignored "-Waix-compat"
+#endif
+
+#include <array>
+#include <cstddef>
+
+#include "test_macros.h"
+
+template <class T, std::size_t Size>
+struct MyArray {
+  T elems[Size];
+};
+
+template <class T>
+void test_type() {
+  {
+    static_assert(sizeof(std::array<T, 0>) == sizeof(T), "");
+    static_assert(alignof(std::array<T, 0>) == alignof(T), "");
+    static_assert(sizeof(std::array<T, 0>) == sizeof(T[1]), "");
+    static_assert(sizeof(std::array<T, 0>) == sizeof(MyArray<T, 1>), "");
+    static_assert(alignof(std::array<T, 0>) == alignof(MyArray<T, 1>), "");
+  }
+
+  {
+    static_assert(sizeof(std::array<T, 1>) == sizeof(T), "");
+    static_assert(alignof(std::array<T, 1>) == alignof(T), "");
+    static_assert(sizeof(std::array<T, 1>) == sizeof(T[1]), "");
+    static_assert(sizeof(std::array<T, 1>) == sizeof(MyArray<T, 1>), "");
+    static_assert(alignof(std::array<T, 1>) == alignof(MyArray<T, 1>), "");
+  }
+
+  {
+    static_assert(sizeof(std::array<T, 2>) == sizeof(T) * 2, "");
+    static_assert(alignof(std::array<T, 2>) == alignof(T), "");
+    static_assert(sizeof(std::array<T, 2>) == sizeof(T[2]), "");
+    static_assert(sizeof(std::array<T, 2>) == sizeof(MyArray<T, 2>), "");
+    static_assert(alignof(std::array<T, 2>) == alignof(MyArray<T, 2>), "");
+  }
+
+  {
+    static_assert(sizeof(std::array<T, 3>) == sizeof(T) * 3, "");
+    static_assert(alignof(std::array<T, 3>) == alignof(T), "");
+    static_assert(sizeof(std::array<T, 3>) == sizeof(T[3]), "");
+    static_assert(sizeof(std::array<T, 3>) == sizeof(MyArray<T, 3>), "");
+    static_assert(alignof(std::array<T, 3>) == alignof(MyArray<T, 3>), "");
+  }
+
+  {
+    static_assert(sizeof(std::array<T, 444>) == sizeof(T) * 444, "");
+    static_assert(alignof(std::array<T, 444>) == alignof(T), "");
+    static_assert(sizeof(std::array<T, 444>) == sizeof(T[444]), "");
+    static_assert(sizeof(std::array<T, 444>) == sizeof(MyArray<T, 444>), "");
+    static_assert(alignof(std::array<T, 444>) == alignof(MyArray<T, 444>), "");
+  }
+}
+
+struct Empty {};
+
+struct Aggregate {
+  int i;
+};
+
+struct WithPadding {
+  long double ld;
+  char c;
+};
+
+#if TEST_STD_VER >= 11
+struct alignas(alignof(std::max_align_t) * 2) Overaligned1 {};
+
+struct alignas(alignof(std::max_align_t) * 2) Overaligned2 {
+  char data[1000];
+};
+
+struct alignas(alignof(std::max_align_t)) Overaligned3 {
+  char data[1000];
+};
+
+struct alignas(8) Overaligned4 {
+  char c;
+};
+
+struct alignas(8) Overaligned5 {};
+#endif
+
+int main(int, char**) {
+  test_type<char>();
+  test_type<short>();
+  test_type<int>();
+  test_type<long>();
+  test_type<long long>();
+  test_type<float>();
+  test_type<double>();
+  test_type<long double>();
+  test_type<char[1]>();
+  test_type<char[2]>();
+  test_type<char[3]>();
+  test_type<Empty>();
+  test_type<Aggregate>();
+  test_type<WithPadding>();
+
+#if TEST_STD_VER >= 11
+  test_type<std::max_align_t>();
+  test_type<Overaligned1>();
+  test_type<Overaligned2>();
+  test_type<Overaligned3>();
+  test_type<Overaligned4>();
+  test_type<Overaligned5>();
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp
deleted file mode 100644
index 6fbc844a11eac5..00000000000000
--- a/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// <array>
-
-// template <class T, size_t N>
-// struct array
-
-// Test the size and alignment matches that of an array of a given type.
-
-// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems.
-#if defined(_AIX)
-# pragma clang diagnostic ignored "-Waix-compat"
-#endif
-
-#include <array>
-#include <iterator>
-#include <type_traits>
-#include <cstddef>
-
-#include "test_macros.h"
-
-template <class T, std::size_t Size>
-struct MyArray {
-  T elems[Size];
-};
-
-template <class T, std::size_t Size>
-void test() {
-  typedef T CArrayT[Size == 0 ? 1 : Size];
-  typedef std::array<T, Size> ArrayT;
-  typedef MyArray<T, Size == 0 ? 1 : Size> MyArrayT;
-  static_assert(sizeof(ArrayT) == sizeof(CArrayT), "");
-  static_assert(sizeof(ArrayT) == sizeof(MyArrayT), "");
-  static_assert(TEST_ALIGNOF(ArrayT) == TEST_ALIGNOF(MyArrayT), "");
-}
-
-template <class T>
-void test_type() {
-  test<T, 1>();
-  test<T, 42>();
-  test<T, 0>();
-}
-
-#if TEST_STD_VER >= 11
-struct alignas(alignof(std::max_align_t) * 2) TestType1 {
-
-};
-
-struct alignas(alignof(std::max_align_t) * 2) TestType2 {
-  char data[1000];
-};
-
-struct alignas(alignof(std::max_align_t)) TestType3 {
-  char data[1000];
-};
-#endif
-
-int main(int, char**) {
-  test_type<char>();
-  test_type<int>();
-  test_type<double>();
-  test_type<long double>();
-
-#if TEST_STD_VER >= 11
-  test_type<std::max_align_t>();
-  test_type<TestType1>();
-  test_type<TestType2>();
-  test_type<TestType3>();
-#endif
-
-  return 0;
-}

>From 480d6ecb8585866d10eefc0f53e312f6cf714fad Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 10:35:14 -0500
Subject: [PATCH 02/10] Rename main() function in compile.pass.cpp test

---
 .../sequences/array/size_and_alignment.compile.pass.cpp       | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
index 66069715103fd7..a4d4e683a16912 100644
--- a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
@@ -101,7 +101,7 @@ struct alignas(8) Overaligned4 {
 struct alignas(8) Overaligned5 {};
 #endif
 
-int main(int, char**) {
+void test() {
   test_type<char>();
   test_type<short>();
   test_type<int>();
@@ -125,6 +125,4 @@ int main(int, char**) {
   test_type<Overaligned4>();
   test_type<Overaligned5>();
 #endif
-
-  return 0;
 }

>From 73f2d0c92319f5d64d3627864aa3e9cb814f37da Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 10:35:49 -0500
Subject: [PATCH 03/10] Rename CharType to EmptyType

---
 libcxx/include/array | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/include/array b/libcxx/include/array
index 334fa747339ba7..fbf114fb69cf77 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -281,10 +281,10 @@ struct _LIBCPP_TEMPLATE_VIS array<_Tp, 0>
     typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
 
     struct _EmptyAggregate { };
-    typedef __conditional_t<is_const<_Tp>::value, const _EmptyAggregate, _EmptyAggregate> _CharType;
+    typedef __conditional_t<is_const<_Tp>::value, const _EmptyAggregate, _EmptyAggregate> _EmptyType;
 
     struct  _ArrayInStructT { _Tp __data_[1]; };
-    _ALIGNAS_TYPE(_ArrayInStructT) _CharType __elems_[sizeof(_ArrayInStructT)];
+    _ALIGNAS_TYPE(_ArrayInStructT) _EmptyType __elems_[sizeof(_ArrayInStructT)];
 
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17
     value_type* data() _NOEXCEPT {return nullptr;}

>From 8ca09a24c7c7dd299d5fc98db5a0d09e41f00ebe Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 11:00:26 -0500
Subject: [PATCH 04/10] Fix usage of alignof in C++03

---
 .../array/size_and_alignment.compile.pass.cpp | 26 +++++++++----------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
index a4d4e683a16912..670e1aae0839ae 100644
--- a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
@@ -33,42 +33,42 @@ template <class T>
 void test_type() {
   {
     static_assert(sizeof(std::array<T, 0>) == sizeof(T), "");
-    static_assert(alignof(std::array<T, 0>) == alignof(T), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 0>) == TEST_ALIGNOF(T), "");
     static_assert(sizeof(std::array<T, 0>) == sizeof(T[1]), "");
     static_assert(sizeof(std::array<T, 0>) == sizeof(MyArray<T, 1>), "");
-    static_assert(alignof(std::array<T, 0>) == alignof(MyArray<T, 1>), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 0>) == TEST_ALIGNOF(MyArray<T, 1>), "");
   }
 
   {
     static_assert(sizeof(std::array<T, 1>) == sizeof(T), "");
-    static_assert(alignof(std::array<T, 1>) == alignof(T), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 1>) == TEST_ALIGNOF(T), "");
     static_assert(sizeof(std::array<T, 1>) == sizeof(T[1]), "");
     static_assert(sizeof(std::array<T, 1>) == sizeof(MyArray<T, 1>), "");
-    static_assert(alignof(std::array<T, 1>) == alignof(MyArray<T, 1>), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 1>) == TEST_ALIGNOF(MyArray<T, 1>), "");
   }
 
   {
     static_assert(sizeof(std::array<T, 2>) == sizeof(T) * 2, "");
-    static_assert(alignof(std::array<T, 2>) == alignof(T), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 2>) == TEST_ALIGNOF(T), "");
     static_assert(sizeof(std::array<T, 2>) == sizeof(T[2]), "");
     static_assert(sizeof(std::array<T, 2>) == sizeof(MyArray<T, 2>), "");
-    static_assert(alignof(std::array<T, 2>) == alignof(MyArray<T, 2>), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 2>) == TEST_ALIGNOF(MyArray<T, 2>), "");
   }
 
   {
     static_assert(sizeof(std::array<T, 3>) == sizeof(T) * 3, "");
-    static_assert(alignof(std::array<T, 3>) == alignof(T), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 3>) == TEST_ALIGNOF(T), "");
     static_assert(sizeof(std::array<T, 3>) == sizeof(T[3]), "");
     static_assert(sizeof(std::array<T, 3>) == sizeof(MyArray<T, 3>), "");
-    static_assert(alignof(std::array<T, 3>) == alignof(MyArray<T, 3>), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 3>) == TEST_ALIGNOF(MyArray<T, 3>), "");
   }
 
   {
     static_assert(sizeof(std::array<T, 444>) == sizeof(T) * 444, "");
-    static_assert(alignof(std::array<T, 444>) == alignof(T), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 444>) == TEST_ALIGNOF(T), "");
     static_assert(sizeof(std::array<T, 444>) == sizeof(T[444]), "");
     static_assert(sizeof(std::array<T, 444>) == sizeof(MyArray<T, 444>), "");
-    static_assert(alignof(std::array<T, 444>) == alignof(MyArray<T, 444>), "");
+    static_assert(TEST_ALIGNOF(std::array<T, 444>) == TEST_ALIGNOF(MyArray<T, 444>), "");
   }
 }
 
@@ -84,13 +84,13 @@ struct WithPadding {
 };
 
 #if TEST_STD_VER >= 11
-struct alignas(alignof(std::max_align_t) * 2) Overaligned1 {};
+struct alignas(TEST_ALIGNOF(std::max_align_t) * 2) Overaligned1 {};
 
-struct alignas(alignof(std::max_align_t) * 2) Overaligned2 {
+struct alignas(TEST_ALIGNOF(std::max_align_t) * 2) Overaligned2 {
   char data[1000];
 };
 
-struct alignas(alignof(std::max_align_t)) Overaligned3 {
+struct alignas(TEST_ALIGNOF(std::max_align_t)) Overaligned3 {
   char data[1000];
 };
 

>From aaa26fad72b916451b74fd9514c0a111ca3f7cbc Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 11:00:39 -0500
Subject: [PATCH 05/10] Fix invalid initialization of empty array

---
 .../std/containers/views/mdspan/extents/CtorTestCombinations.h  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/std/containers/views/mdspan/extents/CtorTestCombinations.h b/libcxx/test/std/containers/views/mdspan/extents/CtorTestCombinations.h
index bf08c580f3746b..18d4f4b61fb231 100644
--- a/libcxx/test/std/containers/views/mdspan/extents/CtorTestCombinations.h
+++ b/libcxx/test/std/containers/views/mdspan/extents/CtorTestCombinations.h
@@ -45,7 +45,7 @@ constexpr void test_construction(AllExtents all_ext) {
 
   // test construction from just dynamic extents
   // create an array of just the extents corresponding to dynamic values
-  std::array<typename AllExtents::value_type, E::rank_dynamic()> dyn_ext{0};
+  std::array<typename AllExtents::value_type, E::rank_dynamic()> dyn_ext{};
   size_t dynamic_idx = 0;
   for (size_t r = 0; r < E::rank(); r++) {
     if (E::static_extent(r) == std::dynamic_extent) {

>From 1d56bf891703848550d3708929d56fb3440e5c77 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 11:35:04 -0500
Subject: [PATCH 06/10] Fix test that was now invalid, and improve the existing
 tests to catch cases with const arrays.

---
 .../default_initializable.compile.pass.cpp    |  2 +-
 .../array/array.cons/initialization.pass.cpp  | 57 ++++++++-----------
 2 files changed, 24 insertions(+), 35 deletions(-)

diff --git a/libcxx/test/std/concepts/concepts.lang/concept.default.init/default_initializable.compile.pass.cpp b/libcxx/test/std/concepts/concepts.lang/concept.default.init/default_initializable.compile.pass.cpp
index 4921a48bcccc17..786c57ebfda118 100644
--- a/libcxx/test/std/concepts/concepts.lang/concept.default.init/default_initializable.compile.pass.cpp
+++ b/libcxx/test/std/concepts/concepts.lang/concept.default.init/default_initializable.compile.pass.cpp
@@ -199,7 +199,7 @@ void test()
     test_not_const<void(Empty::*)(const int&) noexcept(false)>();
 
     // Sequence containers
-    test_not_const<std::array<               int, 0>>();
+    test_true     <std::array<               int, 0>>();
     test_not_const<std::array<               int, 1>>();
     test_false    <std::array<const          int, 1>>();
     test_not_const<std::array<      volatile int, 1>>();
diff --git a/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp b/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
index bdc79e22013f81..be034bf3af4472 100644
--- a/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
@@ -18,38 +18,33 @@ struct NoDefault {
     TEST_CONSTEXPR NoDefault(int) { }
 };
 
-// Test default initialization
-struct test_default_initialization {
+struct test_initialization {
     template <typename T>
     TEST_CONSTEXPR_CXX14 void operator()() const
     {
-        std::array<T, 0> a0; (void)a0;
-        std::array<T, 1> a1; (void)a1;
-        std::array<T, 2> a2; (void)a2;
-        std::array<T, 3> a3; (void)a3;
+        // Check default initalization
+        {
+            std::array<T, 0> a0; (void)a0;
+            // Before C++20, default initialization doesn't work inside constexpr for
+            // trivially default constructible types. This only apply to non-empty arrays,
+            // since empty arrays don't hold an element of type T.
+            if (TEST_STD_AT_LEAST_20_OR_RUNTIME_EVALUATED || !std::is_trivially_default_constructible<T>::value) {
+                std::array<T, 1> a1; (void)a1;
+                std::array<T, 2> a2; (void)a2;
+                std::array<T, 3> a3; (void)a3;
+            }
 
-        std::array<NoDefault, 0> nodefault; (void)nodefault;
-    }
-};
+            std::array<NoDefault, 0> nodefault; (void)nodefault;
+        }
 
-// Additional tests for constexpr default initialization of empty arrays since
-// it seems that making a variable constexpr and merely having a non-constexpr
-// variable in a constexpr function behave differently.
-//
-// Reproducer for https://github.com/llvm/llvm-project/issues/74375
-struct test_default_initialization_74375 {
-    template <typename T>
-    TEST_CONSTEXPR_CXX14 void operator()() const
-    {
-        constexpr std::array<T, 0> a0; (void)a0;
-        constexpr std::array<NoDefault, 0> nodefault; (void)nodefault;
-    }
-};
+        // A const empty array can also be default-initialized regardless of the type
+        // it contains. For non-empty arrays, this doesn't work whenever T doesn't
+        // have a user-provided default constructor.
+        {
+            const std::array<T, 0> a0; (void)a0;
+            const std::array<NoDefault, 0> nodefault; (void)nodefault;
+        }
 
-struct test_nondefault_initialization {
-    template <typename T>
-    TEST_CONSTEXPR_CXX14 void operator()() const
-    {
         // Check direct-list-initialization syntax (introduced in C++11)
     #if TEST_STD_VER >= 11
         {
@@ -188,16 +183,10 @@ TEST_CONSTEXPR_CXX14 bool with_all_types()
 
 int main(int, char**)
 {
-    with_all_types<test_nondefault_initialization>();
-    with_all_types<test_default_initialization>();
-    with_all_types<test_default_initialization_74375>();
+    with_all_types<test_initialization>();
     test_initializer_list();
 #if TEST_STD_VER >= 14
-    static_assert(with_all_types<test_nondefault_initialization>(), "");
-#if TEST_STD_VER >= 20
-    static_assert(with_all_types<test_default_initialization>(), "");
-#endif
-    static_assert(with_all_types<test_default_initialization_74375>(), "");
+    static_assert(with_all_types<test_initialization>(), "");
     static_assert(test_initializer_list(), "");
 #endif
 

>From a54bd374964bdd18555018b83b9b7834d289c7b4 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 12:41:25 -0500
Subject: [PATCH 07/10] Add another test for nonsensical initialization from
 int

---
 .../array/array.cons/initialization.pass.cpp       | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp b/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
index be034bf3af4472..7991d4738d9699 100644
--- a/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/array.cons/initialization.pass.cpp
@@ -181,6 +181,20 @@ TEST_CONSTEXPR_CXX14 bool with_all_types()
     return true;
 }
 
+// This is a regression test -- previously, libc++ would implement empty arrays by
+// storing an array of characters, which means that the array would be initializable
+// from nonsense like an integer (or anything else that can be narrowed to char).
+#if TEST_STD_VER >= 20
+template <class T>
+concept is_list_initializable_int = requires {
+    { T{123} };
+};
+
+struct Foo { };
+static_assert(!is_list_initializable_int<std::array<Foo, 0>>);
+static_assert(!is_list_initializable_int<std::array<Foo, 1>>);
+#endif
+
 int main(int, char**)
 {
     with_all_types<test_initialization>();

>From ab5026981c77efa327615bb304d763dadf97acb5 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 12:43:56 -0500
Subject: [PATCH 08/10] Add tests for emptyness and padding bytes

---
 .../array/size_and_alignment.compile.pass.cpp | 64 +++++++++++--------
 1 file changed, 39 insertions(+), 25 deletions(-)

diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
index 670e1aae0839ae..2e3ad2f5d76627 100644
--- a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
@@ -21,6 +21,7 @@
 
 #include <array>
 #include <cstddef>
+#include <type_traits>
 
 #include "test_macros.h"
 
@@ -32,43 +33,56 @@ struct MyArray {
 template <class T>
 void test_type() {
   {
-    static_assert(sizeof(std::array<T, 0>) == sizeof(T), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 0>) == TEST_ALIGNOF(T), "");
-    static_assert(sizeof(std::array<T, 0>) == sizeof(T[1]), "");
-    static_assert(sizeof(std::array<T, 0>) == sizeof(MyArray<T, 1>), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 0>) == TEST_ALIGNOF(MyArray<T, 1>), "");
+    using Array = std::array<T, 0>;
+    static_assert(sizeof(Array) == sizeof(T), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
+    static_assert(sizeof(Array) == sizeof(T[1]), "");
+    static_assert(sizeof(Array) == sizeof(MyArray<T, 1>), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 1>), "");
+    static_assert(!std::is_empty<Array>::value, "");
+
+    // Make sure empty arrays don't have padding bytes
+    LIBCPP_STATIC_ASSERT(std::__libcpp_datasizeof<Array>::value == sizeof(Array));
   }
 
   {
-    static_assert(sizeof(std::array<T, 1>) == sizeof(T), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 1>) == TEST_ALIGNOF(T), "");
-    static_assert(sizeof(std::array<T, 1>) == sizeof(T[1]), "");
-    static_assert(sizeof(std::array<T, 1>) == sizeof(MyArray<T, 1>), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 1>) == TEST_ALIGNOF(MyArray<T, 1>), "");
+    using Array = std::array<T, 1>;
+    static_assert(sizeof(Array) == sizeof(T), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
+    static_assert(sizeof(Array) == sizeof(T[1]), "");
+    static_assert(sizeof(Array) == sizeof(MyArray<T, 1>), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 1>), "");
+    static_assert(!std::is_empty<Array>::value, "");
   }
 
   {
-    static_assert(sizeof(std::array<T, 2>) == sizeof(T) * 2, "");
-    static_assert(TEST_ALIGNOF(std::array<T, 2>) == TEST_ALIGNOF(T), "");
-    static_assert(sizeof(std::array<T, 2>) == sizeof(T[2]), "");
-    static_assert(sizeof(std::array<T, 2>) == sizeof(MyArray<T, 2>), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 2>) == TEST_ALIGNOF(MyArray<T, 2>), "");
+    using Array = std::array<T, 2>;
+    static_assert(sizeof(Array) == sizeof(T) * 2, "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
+    static_assert(sizeof(Array) == sizeof(T[2]), "");
+    static_assert(sizeof(Array) == sizeof(MyArray<T, 2>), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 2>), "");
+    static_assert(!std::is_empty<Array>::value, "");
   }
 
   {
-    static_assert(sizeof(std::array<T, 3>) == sizeof(T) * 3, "");
-    static_assert(TEST_ALIGNOF(std::array<T, 3>) == TEST_ALIGNOF(T), "");
-    static_assert(sizeof(std::array<T, 3>) == sizeof(T[3]), "");
-    static_assert(sizeof(std::array<T, 3>) == sizeof(MyArray<T, 3>), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 3>) == TEST_ALIGNOF(MyArray<T, 3>), "");
+    using Array = std::array<T, 3>;
+    static_assert(sizeof(Array) == sizeof(T) * 3, "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
+    static_assert(sizeof(Array) == sizeof(T[3]), "");
+    static_assert(sizeof(Array) == sizeof(MyArray<T, 3>), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 3>), "");
+    static_assert(!std::is_empty<Array>::value, "");
   }
 
   {
-    static_assert(sizeof(std::array<T, 444>) == sizeof(T) * 444, "");
-    static_assert(TEST_ALIGNOF(std::array<T, 444>) == TEST_ALIGNOF(T), "");
-    static_assert(sizeof(std::array<T, 444>) == sizeof(T[444]), "");
-    static_assert(sizeof(std::array<T, 444>) == sizeof(MyArray<T, 444>), "");
-    static_assert(TEST_ALIGNOF(std::array<T, 444>) == TEST_ALIGNOF(MyArray<T, 444>), "");
+    using Array = std::array<T, 444>;
+    static_assert(sizeof(Array) == sizeof(T) * 444, "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
+    static_assert(sizeof(Array) == sizeof(T[444]), "");
+    static_assert(sizeof(Array) == sizeof(MyArray<T, 444>), "");
+    static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 444>), "");
+    static_assert(!std::is_empty<Array>::value, "");
   }
 }
 

>From 97fbed6df8cbac07defbf2be1cf899668a3f4a89 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 13 Dec 2023 18:07:12 -0500
Subject: [PATCH 09/10] Fix static assert in C++03 mode

---
 .../sequences/array/size_and_alignment.compile.pass.cpp         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
index 2e3ad2f5d76627..5719b646f3db07 100644
--- a/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/size_and_alignment.compile.pass.cpp
@@ -42,7 +42,7 @@ void test_type() {
     static_assert(!std::is_empty<Array>::value, "");
 
     // Make sure empty arrays don't have padding bytes
-    LIBCPP_STATIC_ASSERT(std::__libcpp_datasizeof<Array>::value == sizeof(Array));
+    LIBCPP_STATIC_ASSERT(std::__libcpp_datasizeof<Array>::value == sizeof(Array), "");
   }
 
   {

>From 3ab2dd974ace97093349b1490cf2951975957019 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 14 Dec 2023 09:58:31 -0500
Subject: [PATCH 10/10] Use __empty

---
 libcxx/include/array | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/include/array b/libcxx/include/array
index fbf114fb69cf77..d00bf278e63545 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -131,6 +131,7 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/remove_cv.h>
+#include <__utility/empty.h>
 #include <__utility/integer_sequence.h>
 #include <__utility/move.h>
 #include <__utility/unreachable.h>
@@ -280,8 +281,7 @@ struct _LIBCPP_TEMPLATE_VIS array<_Tp, 0>
     typedef std::reverse_iterator<iterator>       reverse_iterator;
     typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
 
-    struct _EmptyAggregate { };
-    typedef __conditional_t<is_const<_Tp>::value, const _EmptyAggregate, _EmptyAggregate> _EmptyType;
+    typedef __conditional_t<is_const<_Tp>::value, const __empty, __empty> _EmptyType;
 
     struct  _ArrayInStructT { _Tp __data_[1]; };
     _ALIGNAS_TYPE(_ArrayInStructT) _EmptyType __elems_[sizeof(_ArrayInStructT)];



More information about the libcxx-commits mailing list