c++, libstdc++: Implement C++26 P2747R2 - constexpr placement new [PR115744]

With the PR115754 fix in, constexpr placement new mostly just works,
so this patch just adds constexpr keyword to the placement new operators
in <new>, adds FTMs and testsuite coverage.

There is one accepts-invalid though, the
new (p + 1) int[]{2, 3};      // error (in this paper)
case from the paper.  Can we handle that incrementally?
The problem with that is I think calling operator new now that it is
constexpr should be fine even in that case in constant expressions, so
int *p = std::allocator<int>{}.allocate(3);
int *q = operator new[] (sizeof (int) * 2, p + 1);
should be ok, so it can't be easily the placement new operator call
itself on whose constexpr evaluation we try something special, it should
be on the new expression, but constexpr.cc actually sees only
<<< Unknown tree: expr_stmt
  (void) (TARGET_EXPR <D.2640, (void *) TARGET_EXPR <D.2641, VIEW_CONVERT_EXPR<int *>(b) + 4>>, TARGET_EXPR <D.2642, operator new [] (8, NON_LVALUE_EXPR <D.2640>)>,   int * D.2643;
  <<< Unknown tree: expr_stmt
    (void) (D.2643 = (int *) D.2642) >>>;
and that is just fine by the preexisting constexpr evaluation rules.

Should build_new_1 emit some extra cast for the array cases with placement
new in maybe_constexpr_fn (current_function_decl) that the existing P2738
code would catch?

2024-08-08  Jakub Jelinek  <jakub@redhat.com>

	PR c++/115744
gcc/c-family/
	* c-cppbuiltin.cc (c_cpp_builtins): Change __cpp_constexpr
	from 202306L to 202406L for C++26.
gcc/testsuite/
	* g++.dg/cpp2a/construct_at.h (operator new, operator new[]):
	Use constexpr instead of inline if __cpp_constexpr >= 202406L.
	* g++.dg/cpp26/constexpr-new1.C: New test.
	* g++.dg/cpp26/constexpr-new2.C: New test.
	* g++.dg/cpp26/constexpr-new3.C: New test.
	* g++.dg/cpp26/feat-cxx26.C (__cpp_constexpr): Adjust expected
	value.
libstdc++-v3/
	* libsupc++/new (__glibcxx_want_constexpr_new): Define before
	including bits/version.h.
	(_GLIBCXX_PLACEMENT_CONSTEXPR): Define.
	(operator new, operator new[]): Use it for placement new instead
	of inline.
	* include/bits/version.def (constexpr_new): New FTM.
	* include/bits/version.h: Regenerate.
This commit is contained in:
Jakub Jelinek 2024-08-08 11:05:36 +02:00 committed by Jakub Jelinek
parent e3a6dec326
commit afa3a4a52c
9 changed files with 235 additions and 6 deletions

View file

@ -1091,7 +1091,7 @@ c_cpp_builtins (cpp_reader *pfile)
if (cxx_dialect > cxx23)
{
/* Set feature test macros for C++26. */
cpp_define (pfile, "__cpp_constexpr=202306L");
cpp_define (pfile, "__cpp_constexpr=202406L");
cpp_define (pfile, "__cpp_static_assert=202306L");
cpp_define (pfile, "__cpp_placeholder_variables=202306L");
cpp_define (pfile, "__cpp_structured_bindings=202403L");

View file

@ -0,0 +1,66 @@
// C++26 P2747R2 - constexpr placement new
// { dg-do compile { target c++26 } }
#include "../cpp2a/construct_at.h"
struct S {
constexpr S () : a (42), b (43) {}
constexpr S (int c, int d) : a (c), b (d) {}
int a, b;
};
struct T {
int a, b;
};
constexpr bool
foo ()
{
std::allocator<int> a;
auto b = a.allocate (3);
::new (b) int ();
::new (b + 1) int (1);
::new (b + 2) int {2};
if (b[0] != 0 || b[1] != 1 || b[2] != 2)
return false;
a.deallocate (b, 3);
std::allocator<S> c;
auto d = c.allocate (4);
::new (d) S;
::new (d + 1) S ();
::new (d + 2) S (7, 8);
::new (d + 3) S { 9, 10 };
if (d[0].a != 42 || d[0].b != 43
|| d[1].a != 42 || d[1].b != 43
|| d[2].a != 7 || d[2].b != 8
|| d[3].a != 9 || d[3].b != 10)
return false;
d[0].~S ();
d[1].~S ();
d[2].~S ();
d[3].~S ();
c.deallocate (d, 4);
std::allocator<T> e;
auto f = e.allocate (3);
::new (f) T ();
::new (f + 1) T (7, 8);
::new (f + 2) T { .a = 9, .b = 10 };
if (f[0].a != 0 || f[0].b != 0
|| f[1].a != 7 || f[1].b != 8
|| f[2].a != 9 || f[2].b != 10)
return false;
f[0].~T ();
f[1].~T ();
f[2].~T ();
e.deallocate (f, 3);
auto g = a.allocate (3);
new (g) int[] {1, 2, 3};
if (g[0] != 1 || g[1] != 2 || g[2] != 3)
return false;
new (g) int[] {4, 5};
if (g[0] != 4 || g[1] != 5)
return false;
a.deallocate (g, 3);
return true;
}
static_assert (foo ());

View file

@ -0,0 +1,73 @@
// C++26 P2747R2 - constexpr placement new
// { dg-do compile { target c++26 } }
#include <memory>
#include <new>
#ifndef __cpp_lib_constexpr_new
# error "__cpp_lib_constexpr_new"
#elif __cpp_lib_constexpr_new < 202406L
# error "__cpp_lib_constexpr_new < 202406"
#endif
struct S {
constexpr S () : a (42), b (43) {}
constexpr S (int c, int d) : a (c), b (d) {}
int a, b;
};
struct T {
int a, b;
};
constexpr bool
foo ()
{
std::allocator<int> a;
auto b = a.allocate (3);
::new (b) int ();
::new (b + 1) int (1);
::new (b + 2) int {2};
if (b[0] != 0 || b[1] != 1 || b[2] != 2)
return false;
a.deallocate (b, 3);
std::allocator<S> c;
auto d = c.allocate (4);
::new (d) S;
::new (d + 1) S ();
::new (d + 2) S (7, 8);
::new (d + 3) S { 9, 10 };
if (d[0].a != 42 || d[0].b != 43
|| d[1].a != 42 || d[1].b != 43
|| d[2].a != 7 || d[2].b != 8
|| d[3].a != 9 || d[3].b != 10)
return false;
d[0].~S ();
d[1].~S ();
d[2].~S ();
d[3].~S ();
c.deallocate (d, 4);
std::allocator<T> e;
auto f = e.allocate (3);
::new (f) T ();
::new (f + 1) T (7, 8);
::new (f + 2) T { .a = 9, .b = 10 };
if (f[0].a != 0 || f[0].b != 0
|| f[1].a != 7 || f[1].b != 8
|| f[2].a != 9 || f[2].b != 10)
return false;
f[0].~T ();
f[1].~T ();
f[2].~T ();
e.deallocate (f, 3);
auto g = a.allocate (3);
new (g) int[] {1, 2, 3};
if (g[0] != 1 || g[1] != 2 || g[2] != 3)
return false;
new (g) int[] {4, 5};
if (g[0] != 4 || g[1] != 5)
return false;
a.deallocate (g, 3);
return true;
}
static_assert (foo ());

View file

@ -0,0 +1,47 @@
// C++26 P2747R2 - constexpr placement new
// { dg-do compile { target c++26 } }
#include "../cpp2a/construct_at.h"
struct S {
constexpr S () : a (42), b (43) {}
constexpr S (int c, int d) : a (c), b (d) {}
int a, b;
};
struct T {
int a, b;
};
constexpr bool
foo ()
{
std::allocator<int> a;
auto b = a.allocate (3);
new (b + 1) int[] {2, 3}; // { dg-error "" "" { xfail *-*-* } }
a.deallocate (b, 3);
return true;
}
constexpr bool
bar ()
{
std::allocator<int> a;
auto b = a.allocate (3);
new (b) int[] {1, 2, 3, 4}; // { dg-error "array subscript value '3' is outside the bounds of array 'heap ' of type 'int \\\[3\\\]'" }
a.deallocate (b, 3);
return true;
}
constexpr bool
baz ()
{
std::allocator<int> a;
auto b = a.allocate (2);
new (b) long (42); // { dg-error "accessing value of 'heap ' through a 'long int' glvalue in a constant expression" }
a.deallocate (b, 2);
return true;
}
constexpr bool a = foo ();
constexpr bool b = bar ();
constexpr bool c = baz ();

View file

@ -134,8 +134,8 @@
#ifndef __cpp_constexpr
# error "__cpp_constexpr"
#elif __cpp_constexpr != 202306L
# error "__cpp_constexpr != 202306L"
#elif __cpp_constexpr != 202406L
# error "__cpp_constexpr != 202406L"
#endif
#ifndef __cpp_decltype_auto

View file

@ -58,5 +58,18 @@ namespace std
{ l->~T (); }
}
inline void *operator new (std::size_t, void *p) noexcept
#if __cpp_constexpr >= 202406L
constexpr
#else
inline
#endif
void *operator new (std::size_t, void *p) noexcept
{ return p; }
#if __cpp_constexpr >= 202406L
constexpr
#else
inline
#endif
void *operator new[] (std::size_t, void *p) noexcept
{ return p; }

View file

@ -1838,6 +1838,15 @@ ftms = {
};
};
ftms = {
name = constexpr_new;
values = {
v = 202406;
cxxmin = 26;
extra_cond = "__cpp_constexpr >= 202406L";
};
};
// Standard test specifications.
stds[97] = ">= 199711L";
stds[03] = ">= 199711L";

View file

@ -2033,4 +2033,14 @@
#endif /* !defined(__cpp_lib_ranges_concat) && defined(__glibcxx_want_ranges_concat) */
#undef __glibcxx_want_ranges_concat
#if !defined(__cpp_lib_constexpr_new)
# if (__cplusplus > 202302L) && (__cpp_constexpr >= 202406L)
# define __glibcxx_constexpr_new 202406L
# if defined(__glibcxx_want_all) || defined(__glibcxx_want_constexpr_new)
# define __cpp_lib_constexpr_new 202406L
# endif
# endif
#endif /* !defined(__cpp_lib_constexpr_new) && defined(__glibcxx_want_constexpr_new) */
#undef __glibcxx_want_constexpr_new
#undef __glibcxx_want_all

View file

@ -43,6 +43,7 @@
#define __glibcxx_want_launder
#define __glibcxx_want_hardware_interference_size
#define __glibcxx_want_destroying_delete
#define __glibcxx_want_constexpr_new
#include <bits/version.h>
#pragma GCC visibility push(default)
@ -175,12 +176,22 @@ void operator delete[](void*, std::size_t, std::align_val_t)
#endif // __cpp_sized_deallocation
#endif // __cpp_aligned_new
#if __cpp_lib_constexpr_new >= 202406L
# define _GLIBCXX_PLACEMENT_CONSTEXPR constexpr
#else
# define _GLIBCXX_PLACEMENT_CONSTEXPR inline
#endif
// Default placement versions of operator new.
_GLIBCXX_NODISCARD inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
_GLIBCXX_NODISCARD _GLIBCXX_PLACEMENT_CONSTEXPR
void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
_GLIBCXX_NODISCARD inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
_GLIBCXX_NODISCARD _GLIBCXX_PLACEMENT_CONSTEXPR
void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
#undef _GLIBCXX_PLACEMENT_CONSTEXPR
// Default placement versions of operator delete.
inline void operator delete (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
inline void operator delete[](void*, void*) _GLIBCXX_USE_NOEXCEPT { }