[libc++] Complete overhaul of constexpr support in std::array

This commit adds missing support for constexpr in std::array under all
standard modes up to and including C++20. It also transforms the <array>
tests to check for constexpr-friendliness under the right standard modes.

Fixes https://llvm.org/PR40124
Fixes rdar://57522096
Supersedes https://reviews.llvm.org/D60666

Differential Revision: https://reviews.llvm.org/D80452
diff --git a/libcxx/test/std/containers/sequences/array/at.pass.cpp b/libcxx/test/std/containers/sequences/array/at.pass.cpp
index 0454643..ed4ab80 100644
--- a/libcxx/test/std/containers/sequences/array/at.pass.cpp
+++ b/libcxx/test/std/containers/sequences/array/at.pass.cpp
@@ -8,10 +8,7 @@
 
 // <array>
 
-// reference operator[] (size_type)
-// const_reference operator[] (size_type); // constexpr in C++14
-// reference at (size_type)
-// const_reference at (size_type); // constexpr in C++14
+// reference at (size_type); // constexpr in C++17
 
 #include <array>
 #include <cassert>
@@ -26,100 +23,91 @@
 // Disable the missing braces warning for this reason.
 #include "disable_missing_braces_warning.h"
 
-#if TEST_STD_VER > 14
-constexpr bool check_idx( size_t idx, double val )
-{
-    std::array<double, 3> arr = {1, 2, 3.5};
-    return arr.at(idx) == val;
-}
-#endif
 
-int main(int, char**)
+TEST_CONSTEXPR_CXX17 bool tests()
 {
     {
         typedef double T;
         typedef std::array<T, 3> C;
         C c = {1, 2, 3.5};
-        C::reference r1 = c.at(0);
+        typename C::reference r1 = c.at(0);
         assert(r1 == 1);
         r1 = 5.5;
-        assert(c.front() == 5.5);
+        assert(c[0] == 5.5);
 
-        C::reference r2 = c.at(2);
+        typename C::reference r2 = c.at(2);
         assert(r2 == 3.5);
         r2 = 7.5;
-        assert(c.back() == 7.5);
-
-#ifndef TEST_HAS_NO_EXCEPTIONS
-        try
-        {
-            TEST_IGNORE_NODISCARD  c.at(3);
-            assert(false);
-        }
-        catch (const std::out_of_range &) {}
-#endif
+        assert(c[2] == 7.5);
     }
+    return true;
+}
+
+void test_exceptions()
+{
 #ifndef TEST_HAS_NO_EXCEPTIONS
     {
-        typedef double T;
-        typedef std::array<T, 0> C;
-        C c = {};
-        C const& cc = c;
-        try
-        {
-            TEST_IGNORE_NODISCARD  c.at(0);
+        std::array<int, 4> array = {1, 2, 3, 4};
+
+        try {
+            TEST_IGNORE_NODISCARD array.at(4);
+            assert(false);
+        } catch (std::out_of_range const&) {
+            // pass
+        } catch (...) {
             assert(false);
         }
-        catch (const std::out_of_range &) {}
-        try
-        {
-            TEST_IGNORE_NODISCARD  cc.at(0);
+
+        try {
+            TEST_IGNORE_NODISCARD array.at(5);
+            assert(false);
+        } catch (std::out_of_range const&) {
+            // pass
+        } catch (...) {
             assert(false);
         }
-        catch (const std::out_of_range &) {}
-    }
-#endif
-    {
-        typedef double T;
-        typedef std::array<T, 3> C;
-        const C c = {1, 2, 3.5};
-        C::const_reference r1 = c.at(0);
-        assert(r1 == 1);
 
-        C::const_reference r2 = c.at(2);
-        assert(r2 == 3.5);
-
-#ifndef TEST_HAS_NO_EXCEPTIONS
-        try
-        {
-            TEST_IGNORE_NODISCARD  c.at(3);
+        try {
+            TEST_IGNORE_NODISCARD array.at(6);
+            assert(false);
+        } catch (std::out_of_range const&) {
+            // pass
+        } catch (...) {
             assert(false);
         }
-        catch (const std::out_of_range &) {}
-#endif
+
+        try {
+            TEST_IGNORE_NODISCARD array.at(-1);
+            assert(false);
+        } catch (std::out_of_range const&) {
+            // pass
+        } catch (...) {
+            assert(false);
+        }
     }
 
-#if TEST_STD_VER > 11
     {
-        typedef double T;
-        typedef std::array<T, 3> C;
-        constexpr C c = {1, 2, 3.5};
+        std::array<int, 0> array = {};
 
-        constexpr T t1 = c.at(0);
-        static_assert (t1 == 1, "");
-
-        constexpr T t2 = c.at(2);
-        static_assert (t2 == 3.5, "");
+        try {
+            TEST_IGNORE_NODISCARD array.at(0);
+            assert(false);
+        } catch (std::out_of_range const&) {
+            // pass
+        } catch (...) {
+            assert(false);
+        }
     }
 #endif
+}
 
-#if TEST_STD_VER > 14
-    {
-        static_assert (check_idx(0, 1), "");
-        static_assert (check_idx(1, 2), "");
-        static_assert (check_idx(2, 3.5), "");
-    }
+int main(int, char**)
+{
+    tests();
+    test_exceptions();
+
+#if TEST_STD_VER >= 17
+    static_assert(tests(), "");
 #endif
-
-  return 0;
+    return 0;
 }