diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -9358,8 +9358,7 @@ bool IsNothrow = false; bool IsPlacement = false; if (OperatorNew->isReservedGlobalPlacementOperator() && - Info.CurrentCall->isStdFunction() && !E->isArray()) { - // FIXME Support array placement new. + Info.CurrentCall->isStdFunction()) { assert(E->getNumPlacementArgs() == 1); if (!EvaluatePointer(E->getPlacementArg(0), Result, Info)) return false; diff --git a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp --- a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp +++ b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp @@ -96,14 +96,28 @@ return a == 42; } +void *operator new[](std::size_t, void *p) { return p; } +constexpr bool no_array_placement_new_in_user_code() { // expected-error {{never produces a constant expression}} + int a[3]; + new (&a) int[3](); // expected-note {{call to placement 'operator new[]'}} + return a[0] == 0 && a[1] == 0 && a[2] == 0; +} + namespace std { constexpr bool placement_new_in_stdlib() { int a; new (&a) int(42); return a == 42; } + + constexpr bool array_placement_new_in_stdlib() { + int a[3]; + new (&a) int[3]{42, 43, 44}; + return a[0] == 42 && a[1] == 43 && a[2] == 44; + } } static_assert(std::placement_new_in_stdlib()); +static_assert(std::array_placement_new_in_stdlib()); namespace std { template diff --git a/libcxx/include/__memory/construct_at.h b/libcxx/include/__memory/construct_at.h --- a/libcxx/include/__memory/construct_at.h +++ b/libcxx/include/__memory/construct_at.h @@ -15,6 +15,7 @@ #include <__iterator/access.h> #include <__memory/addressof.h> #include <__utility/forward.h> +#include #include #include @@ -34,7 +35,12 @@ _LIBCPP_INLINE_VISIBILITY constexpr _Tp* construct_at(_Tp* __location, _Args&& ...__args) { _LIBCPP_ASSERT(__location, "null pointer given to construct_at"); - return ::new ((void*)__location) _Tp(_VSTD::forward<_Args>(__args)...); + auto __ptr = ::new ((void*)__location) _Tp(_VSTD::forward<_Args>(__args)...); + // This implements https://wg21.link/LWG3436, which hasn't been voted into the Standard yet. + if constexpr (is_array_v<_Tp>) + return _VSTD::launder(__location); + else + return __ptr; } #endif diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.LWG3436.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.LWG3436.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.LWG3436.pass.cpp @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// Only recent Clang versions support array placement new in constant expressions. +// XFAIL: clang-12, clang-13, apple-clang-12, apple-clang-13 + +// This test ensures that we implement the resolution of https://wg21.link/LWG3436, +// which mentions that std::construct_at doesn't work with array types. +// +// Libc++ currently implements this LWG issue even though it hasn't been voted +// into the Standard yet, because failure to implement this issue makes it +// impossible (well, very difficult) to implement other features, such as support +// for arrays in shared_ptr. +// +// Note: libc++ currently implements the proposed resolution as of 2021-01-16. +// If the proposed resolution that ends up being adopted changes, we'll +// want to update our implementation (and probably this test too). +// +// REQUIRES: stdlib=libc++ + +#include +#include +#include + +struct CountInitializations { + CountInitializations() { ++constructions; } + static int constructions; +}; + +int CountInitializations::constructions = 0; + +// Note that we don't test std::construct_at(&array, arguments...) because that +// is ill-formed. That would call array placement new with a new-initializer +// like '( expression-list )', which is not allowed (arrays require a braced-init-list). +constexpr bool test() { + { + using Array = int[1]; + Array array; + std::same_as auto result = std::construct_at(&array); + assert(result == &array); + assert(array[0] == 0); + } + { + using Array = int[2]; + Array array; + std::same_as auto result = std::construct_at(&array); + assert(result == &array); + assert(array[0] == 0); + assert(array[1] == 0); + } + { + using Array = int[3]; + Array array; + std::same_as auto result = std::construct_at(&array); + assert(result == &array); + assert(array[0] == 0); + assert(array[1] == 0); + assert(array[2] == 0); + } + + return true; +} + +void test_construction_counts() { + { + using Array = CountInitializations[1]; + CountInitializations array[1]; + CountInitializations::constructions = 0; + std::construct_at(&array); + assert(CountInitializations::constructions == 1); + } + { + using Array = CountInitializations[2]; + CountInitializations array[2]; + CountInitializations::constructions = 0; + std::construct_at(&array); + assert(CountInitializations::constructions == 2); + } + { + using Array = CountInitializations[3]; + CountInitializations array[3]; + CountInitializations::constructions = 0; + std::construct_at(&array); + assert(CountInitializations::constructions == 3); + } +} + +int main(int, char**) { + test(); + test_construction_counts(); + static_assert(test()); + return 0; +}