Bug 91369 - Implement P0784R7: constexpr new
Summary: Implement P0784R7: constexpr new
Status: REOPENED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 10.0
: P3 normal
Target Milestone: 10.0
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks: 88323
  Show dependency treegraph
 
Reported: 2019-08-05 16:45 UTC by Marek Polacek
Modified: 2019-11-01 23:26 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2019-08-05 00:00:00


Attachments
gcc10-pr91369.patch (9.87 KB, patch)
2019-09-26 07:51 UTC, Jakub Jelinek
Details | Diff
gcc10-pr91369.patch (14.12 KB, patch)
2019-09-26 19:31 UTC, Jakub Jelinek
Details | Diff
gcc10-pr91369.patch (16.35 KB, patch)
2019-09-27 19:17 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Marek Polacek 2019-08-05 16:45:39 UTC
As per <http://wg21.link/p0784r7>.
Comment 1 Jakub Jelinek 2019-08-30 17:25:16 UTC
Just playing around a little bit, on:
struct S { constexpr S () : s (5) {}; int s; };
constexpr bool foo ()
{
  S *p = new S ();
  delete p;
  return true;
}
(so, trivial dtor rather than introducing constexpr dtors for now):

--- gcc/cp/constexpr.c.jj	2019-08-27 12:26:39.335884288 +0200
+++ gcc/cp/constexpr.c	2019-08-30 18:49:59.673689644 +0200
@@ -1666,6 +1666,34 @@ cxx_eval_call_expression (const constexp
 					   lval, non_constant_p, overflow_p);
   if (!DECL_DECLARED_CONSTEXPR_P (fun))
     {
+      if (cxx_dialect >= cxx2a
+          && IDENTIFIER_NEWDEL_OP_P (DECL_NAME (fun))
+	  && CP_DECL_CONTEXT (fun) == global_namespace)
+	{
+	  if (IDENTIFIER_NEW_OP_P (DECL_NAME (fun)))
+	    {
+	      const int nargs = call_expr_nargs (t);
+	      tree sz = NULL_TREE;
+	      for (int i = 0; i < nargs; ++i)
+		{
+		  tree arg = CALL_EXPR_ARG (t, i);
+		  arg = cxx_eval_constant_expression (ctx, arg, false,
+						      non_constant_p,
+						      overflow_p);
+		  VERIFY_CONSTANT (arg);
+		  if (i == 0)
+		    sz = arg;
+		}
+	      gcc_assert (sz);
+	      tree type = build_array_type_nelts (char_type_node,
+						  tree_to_uhwi (sz));
+	      tree var = build_decl (loc, VAR_DECL, NULL_TREE, type);
+	      DECL_ARTIFICIAL (var) = 1;
+	      return fold_convert (ptr_type_node, build_address (var));
+	    }
+	  else
+	    /* FIXME */;
+	}
       if (!ctx->quiet)
 	{
 	  if (!lambda_static_thunk_p (fun))
@@ -6243,7 +6271,12 @@ potential_constant_expression_1 (tree t,
 		if (!DECL_DECLARED_CONSTEXPR_P (fun)
 		    /* Allow any built-in function; if the expansion
 		       isn't constant, we'll deal with that then.  */
-		    && !fndecl_built_in_p (fun))
+		    && !fndecl_built_in_p (fun)
+		    /* In C++2a, replaceable global allocation functions
+		       are constant expressions.  */
+		    && (cxx_dialect < cxx2a
+			|| !IDENTIFIER_NEWDEL_OP_P (DECL_NAME (fun))
+			|| CP_DECL_CONTEXT (fun) != global_namespace))
 		  {
 		    if (flags & tf_error)
 		      {

This fails because we represent the new expression as COMPOUND_EXPR which first
calls the allocation function (handled by the above code) and then constructs the var (effectively through a reinterpret cast not marked as such), and fail on that:
      r = cxx_fold_indirect_ref (EXPR_LOCATION (t), TREE_TYPE (t), op0,
                                 &empty_base);
      if (r == NULL_TREE)
        {
          /* We couldn't fold to a constant value.  Make sure it's not
             something we should have been able to fold.  */
          tree sub = op0;
          STRIP_NOPS (sub);
          if (TREE_CODE (sub) == ADDR_EXPR)
            {
              gcc_assert (!same_type_ignoring_top_level_qualifiers_p
                          (TREE_TYPE (TREE_TYPE (sub)), TREE_TYPE (t)));
              /* DR 1188 says we don't have to deal with this.  */
              if (!ctx->quiet)
                error ("accessing value of %qE through a %qT glvalue in a "
                       "constant expression", build_fold_indirect_ref (sub),
                       TREE_TYPE (t));
              *non_constant_p = true;
              return t;
            }

          if (lval && op0 != orig_op0)
            return build1 (INDIRECT_REF, TREE_TYPE (t), op0);
          if (!lval)
            VERIFY_CONSTANT (t);
          return t;
        }

So, first of all, is it a good idea to represent the HEAP variables through artifical VAR_DECLs?

I guess in the outermost constexpr evaluation context we'd need to track which of those we have allocated and deallocated and do the checking that at the end of outermost constexpr evaluation no allocations are left around, and that we don't deallocate something that hasn't been allocated.

As we don't have the actual dynamic type they will have at the end of new expression, should we mark them some way and either allow the first cxx_fold_indirect_ref or the above code to change their type the first time they are stored?

Is placement new ok in constexpr contexts or not?

Can the global replaceable allocation functions be marked constexpr by the user and if so, what should happen in that case, shall they be still treated as magic allocation functions, something else?

For the constexpr dtors, we need to make sure to mark trivial dtors as constexpr, and default dtors as constexpr if they satisfy the rules, verify the restrictions for user defined constexpr dtors, and check for constexpr dtor in literal type check.  Plus guess for constexpr variables we need to at some point verify they are constexpr destructible (but on a copy of the variable value, such that changes to the value during destruction don't modify the actual value of the variable we use normally).
Comment 2 Jason Merrill 2019-09-10 02:06:33 UTC
(In reply to Jakub Jelinek from comment #1)
> So, first of all, is it a good idea to represent the HEAP variables through
> artifical VAR_DECLs?

That makes sense to me.
 
> I guess in the outermost constexpr evaluation context we'd need to track
> which of those we have allocated and deallocated and do the checking that at
> the end of outermost constexpr evaluation no allocations are left around,

Sounds right.

> and that we don't deallocate something that hasn't been allocated.

I'd expect that to be detected during evaluation when we try to deallocate something that isn't constant or isn't the address of one of these artificial VAR_DECLs.

> As we don't have the actual dynamic type they will have at the end of new
> expression

We should really fix that.  Perhaps we should delay lowering new-expressions until genericization time?

> should we mark them some way and either allow the first
> cxx_fold_indirect_ref or the above code to change their type the first time
> they are stored?

This would work, but wouldn't distinguish between a new-expression and the equivalent written by hand.  I suppose a flag could make that distinction.

> Is placement new ok in constexpr contexts or not?

No, placement new is not replaceable.
 
> Can the global replaceable allocation functions be marked constexpr by the
> user?

No.

> For the constexpr dtors, we need to make sure to mark trivial dtors as
> constexpr, and default dtors as constexpr if they satisfy the rules, verify
> the restrictions for user defined constexpr dtors, and check for constexpr
> dtor in literal type check.  Plus guess for constexpr variables we need to
> at some point verify they are constexpr destructible (but on a copy of the
> variable value, such that changes to the value during destruction don't
> modify the actual value of the variable we use normally).

Right.  I think that's implied by the passage "An object a is said to have constant destruction if ... for a hypothetical expression e whose only effect is to destroy a, e would be a core constant expression if the lifetime of a and its non-mutable subobjects (but not its mutable subobjects) were considered to start within e."
Comment 3 Jakub Jelinek 2019-09-10 08:02:51 UTC
(In reply to Jason Merrill from comment #2)
> > should we mark them some way and either allow the first
> > cxx_fold_indirect_ref or the above code to change their type the first time
> > they are stored?
> 
> This would work, but wouldn't distinguish between a new-expression and the
> equivalent written by hand.  I suppose a flag could make that distinction.

Wouldn't user written code have REINTERPRET_CAST_P set on the cast and thus be rejected?
Can user call the replaceable new operator directly in constexpr contexts in some valid way?
Comment 4 Jason Merrill 2019-09-10 14:27:50 UTC
(In reply to Jakub Jelinek from comment #3)
> (In reply to Jason Merrill from comment #2)
> > > should we mark them some way and either allow the first
> > > cxx_fold_indirect_ref or the above code to change their type the first time
> > > they are stored?
> > 
> > This would work, but wouldn't distinguish between a new-expression and the
> > equivalent written by hand.  I suppose a flag could make that distinction.
> 
> Wouldn't user written code have REINTERPRET_CAST_P set on the cast and thus
> be rejected?
> Can user call the replaceable new operator directly in constexpr contexts in
> some valid way?

Ah, good point.  They could call it directly, but couldn't do anything with the memory without a reinterpret_cast.
Comment 5 Jakub Jelinek 2019-09-26 07:51:39 UTC
Created attachment 46946 [details]
gcc10-pr91369.patch

Current WIP patch.
Comment 6 Jakub Jelinek 2019-09-26 08:15:04 UTC
Still need to add further testcase coverage and finish cookie support, but I ran into something that looks like a bug in the C++ standard.

[dcl.constexpr]/3 says:
if the function is a constructor or destructor, its class shall not have any virtual base classes;
[dcl.constexpr]/5 says:
The definition of a constexpr destructor whose function-body is not = delete shall additionally satisfy the following requirement:
- for every subobject of class type or (possibly multi-dimensional) array thereof, that class type shall have a constexpr destructor.
[class.dtor]/8 says:
A destructor is trivial if it is not user-provided and if:
- the destructor is not virtual,
- all of the direct base classes of its class have trivial destructors, and
- for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
[class.dtor]/9 says:
A defaulted destructor is a constexpr destructor if it satisfies the requirements for a constexpr destructor ([dcl.constexpr]).
[ Note: In particular, a trivial destructor is a constexpr destructor.
— end note ]

Now, consider the g++.old-deja/g++.mike/p12306a.C or g++.dg/abi/empty7.C testcases in -std=c++2a modes, let's say the first one:

class a {
public:
    int i;
};

class g : virtual public a {
};

class b : virtual public a {
    int j;
};

class c : public g, public b {
};

class d {
public:
    virtual class b* get() {return 0;}
};

class f : public d {
public:
    virtual class b* get() {return &_c;}
    c _c;
};

int main(void) {
    f D;
    b* bp=D.get();
    D._c.i = 42;
    return &D._c.i != &bp->i;
}

The patch ICEs on this, because the implicit f::~f () is considered trivial (there are no virtual destructors in the testcase, nor any user-provided destructors),
but it isn't a constexpr destructor, because while f doesn't have virtual bases, a non-static data member of f (_c) has virtual bases and thus c::~c () is not a constexpr destructor and because of that f::~f () can't be a constexpr destructor either.

So, is the [class.dtor]/9 note just incorrect and should be removed, or clarified somehow?  I believe it shouldn't affect what actually is a literal type or not, because
the constructor of a class without virtual bases which has a non-static data member that has virtual bases can't be constexpr either.

On the compiler side, it would mean the trivial destructor quick bail-out checks wouldn't actually work for when we ask whether the destructor is constexpr, though we could keep it perhaps in the literal_type_p checking, at least as long as a subobject with virtual bases really always forces non-constexpr constructors.
Comment 7 Jason Merrill 2019-09-26 14:33:31 UTC
(In reply to Jakub Jelinek from comment #6)
> So, is the [class.dtor]/9 note just incorrect and should be removed, or
> clarified somehow?  I believe it shouldn't affect what actually is a literal
> type or not, because the constructor of a class without virtual bases which
> has a non-static data member that has virtual bases can't be constexpr either.

I think a virtual base just shouldn't make the destructor non-constexpr, which would make the note correct.  I've emailed CWG to that effect.
Comment 8 Jakub Jelinek 2019-09-26 19:31:52 UTC
Created attachment 46956 [details]
gcc10-pr91369.patch

Updated patch.
This patch implements the state if C++ drops the note that trivial destructors
are constexpr destructors.  If on the core reflector you agree on something
else, please let me know and I'll change it.
Other than that, I need to finish up the cookie_size support for array new
(trying to represent that heap object as record with two fields, sizetype array
covering cookie and class_type array covering the rest, but seems indirect ref
folding isn't still happy about that, otherwise I'm out of ideas what else to
test.
Comment 9 Jakub Jelinek 2019-09-27 19:17:21 UTC
Created attachment 46967 [details]
gcc10-pr91369.patch

Updated patch.
Comment 10 Jakub Jelinek 2019-10-05 07:38:53 UTC
Author: jakub
Date: Sat Oct  5 07:38:21 2019
New Revision: 276622

URL: https://gcc.gnu.org/viewcvs?rev=276622&root=gcc&view=rev
Log:
	PR c++/91369 - Implement P0784R7: constexpr new
c-family/
	* c-cppbuiltin.c (c_cpp_builtins): Predefine
	__cpp_constexpr_dynamic_alloc=201907 for -std=c++2a.
cp/
	* cp-tree.h (enum cp_tree_index): Add CPTI_HEAP_UNINIT_IDENTIFIER,
	CPTI_HEAP_IDENTIFIER and CPTI_HEAP_DELETED_IDENTIFIER.
	(heap_uninit_identifier, heap_identifier, heap_deleted_identifier):
	Define.
	(type_has_constexpr_destructor, build_new_constexpr_heap_type,
	cxx_constant_dtor): Declare.
	* class.c (type_maybe_constexpr_default_constructor): Make static.
	(type_maybe_constexpr_destructor, type_has_constexpr_destructor): New
	functions.
	(finalize_literal_type_property): For c++2a, don't clear
	CLASSTYPE_LITERAL_P for types without trivial destructors unless they
	have non-constexpr destructors.
	(explain_non_literal_class): For c++2a, complain about non-constexpr
	destructors rather than about non-trivial destructors.
	* constexpr.c: Include stor-layout.h.
	(struct constexpr_global_ctx): New type.
	(struct constexpr_ctx): Add global field, remove values and
	constexpr_ops_count.
	(cxx_replaceable_global_alloc_fn): New inline function.
	(cxx_eval_call_expression): For c++2a allow calls to replaceable
	global allocation functions, for new return address of a heap uninit
	var, for delete record its deletion.  Change ctx->values->{get,put} to
	ctx->global->values.{get,put}.
	(non_const_var_error): Add auto_diagnostic_group sentinel.  Emit
	special diagnostics for heap variables.
	(cxx_eval_store_expression): Change ctx->values->{get,put} to
	ctx->global->values.{get,put}.
	(cxx_eval_loop_expr): Initialize jump_target if NULL.  Change
	new_ctx.values->remove to ctx->global->values.remove.
	(cxx_eval_constant_expression): Change *ctx->constexpr_ops_count
	to ctx->global->constexpr_ops_count.  Change ctx->values->{get,put} to
	ctx->global->values.{get,put}.
	<case NOP_EXPR>: Formatting fix.  On cast of replaceable global
	allocation function to some pointer type, adjust the type of
	the heap variable and change name from heap_uninit_identifier
	to heap_identifier.
	(find_heap_var_refs): New function.
	(cxx_eval_outermost_constant_expr): Add constexpr_dtor argument,
	handle evaluation of constexpr dtors and add tracking of heap
	variables.  Use tf_no_cleanup for get_target_expr_with_sfinae.
	(cxx_constant_value): Adjust cxx_eval_outermost_constant_expr caller.
	(cxx_constant_dtor): New function.
	(maybe_constant_value, fold_non_dependent_expr_template,
	maybe_constant_init_1): Adjust cxx_eval_outermost_constant_expr
	callers.
	(potential_constant_expression_1): Ignore clobbers.  Allow
	COND_EXPR_IS_VEC_DELETE for c++2a.
	* decl.c (initialize_predefined_identifiers): Add heap identifiers.
	(decl_maybe_constant_destruction): New function.
	(cp_finish_decl): Don't clear TREE_READONLY for constexpr variables
	with non-trivial, but constexpr destructors.
	(register_dtor_fn): For constexpr variables with constexpr non-trivial
	destructors call cxx_maybe_build_cleanup instead of adding destructor
	calls at runtime.
	(expand_static_init): For constexpr variables with constexpr
	non-trivial destructors call cxx_maybe_build_cleanup.
	(grokdeclarator): Allow constexpr destructors for c++2a.  Formatting
	fix.
	(cxx_maybe_build_cleanup): For constexpr variables with constexpr
	non-trivial destructors call cxx_constant_dtor instead of adding
	destructor calls at runtime.
	* init.c: Include stor-layout.h.
	(build_new_constexpr_heap_type, maybe_wrap_new_for_constexpr): New
	functions.
	(build_new_1): For c++2a and new[], add cast around the alloc call
	to help constexpr evaluation figure out the type of the heap storage.
	(build_vec_delete_1): Set DECL_INITIAL of tbase and emit a DECL_EXPR
	for it instead of initializing an uninitialized variable.
	* method.c: Include intl.h.
	(SFK_CTOR_P, SFK_DTOR_P, SFK_ASSIGN_P, SFK_COPY_P, SFK_MOVE_P): Move
	definitions earlier.
	(process_subob_fn): Add sfk argument, adjust non-constexpr call
	diagnostics based on it.
	(walk_field_subobs): Formatting fixes.  Adjust process_subob_fn caller.
	(synthesized_method_base_walk): Likewise.
	(synthesized_method_walk): Set *constexpr_p to true for dtors in c++2a.
	Fix up DR number in comment.
	(implicitly_declare_fn): Formatting fix.
	* typeck2.c (store_init_value): Don't call cp_fully_fold_init on
	initializers of automatic non-constexpr variables in constexpr
	functions.
testsuite/
	* g++.dg/cpp0x/constexpr-delete2.C: Adjust expected diagnostics for
	c++2a.
	* g++.dg/cpp0x/locations1.C: Only expect constexpr ~S() diagnostics
	in c++17_down, adjust expected wording.
	* g++.dg/cpp1y/constexpr-new.C: Only expect diagnostics in c++17_down.
	* g++.dg/cpp2a/constexpr-dtor1.C: New test.
	* g++.dg/cpp2a/constexpr-dtor2.C: New test.
	* g++.dg/cpp2a/constexpr-dtor3.C: New test.
	* g++.dg/cpp2a/constexpr-new1.C: New test.
	* g++.dg/cpp2a/constexpr-new2.C: New test.
	* g++.dg/cpp2a/constexpr-new3.C: New test.
	* g++.dg/cpp2a/constexpr-new4.C: New test.
	* g++.dg/cpp2a/feat-cxx2a.C: Add __cpp_constinit and
	__cpp_constexpr_dynamic_alloc tests.  Tweak __cpp_* tests for c++2a
	features to use style like older features, including #ifdef test.
	* g++.dg/ext/is_literal_type3.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-dtor1.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-dtor2.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-dtor3.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new1.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new2.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new4.C
    trunk/gcc/testsuite/g++.dg/ext/is_literal_type3.C
Modified:
    trunk/gcc/c-family/ChangeLog
    trunk/gcc/c-family/c-cppbuiltin.c
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/class.c
    trunk/gcc/cp/constexpr.c
    trunk/gcc/cp/cp-tree.h
    trunk/gcc/cp/decl.c
    trunk/gcc/cp/init.c
    trunk/gcc/cp/method.c
    trunk/gcc/cp/typeck2.c
    trunk/gcc/testsuite/ChangeLog
    trunk/gcc/testsuite/g++.dg/cpp0x/constexpr-delete2.C
    trunk/gcc/testsuite/g++.dg/cpp0x/locations1.C
    trunk/gcc/testsuite/g++.dg/cpp1y/constexpr-new.C
    trunk/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
Comment 11 Jason Merrill 2019-10-15 01:23:09 UTC
Implemented.
Comment 12 Jakub Jelinek 2019-10-15 06:54:00 UTC
Well, not fully.  The paper has additions of constexpr keywords on the library side, but more importantly, we'll probably need some hacks in the compiler for the library side, but waiting for Jonathan with that.

My understanding is that right now (perhaps that might change) placement new should not be treated as constexpr function, but it needs to be in certain STL templates (at least std::construct_at, something else?)

Also, I was afraid that in std::allocator<T>::allocate, the cast would be reinterpret_cast, but apparently it isn't:

template <typename T>
constexpr T *
foo ()
{
  return static_cast<T *> (::operator new (sizeof (T)));
}

constexpr int
bar ()
{
  auto a = foo <int> ();
  ::operator delete (a);
  return 0;
}

constexpr auto p = bar ();

(this doesn't actually use placement new, because that does and should ATM fail).  So, maybe it is just the placement new that needs to be handled.
But, if one can cast the global replaceable allocator function result to anything in constexpr and our implementation triggers on the cast rather than on some spot coming from the new operator, shouldn't we use some new C++ expression kind or say an internal function as an explicit cast in new expression (placement or not) rather than any cast of the pointer?

Note, clang++ rejects the above testcase with
/tmp/6.C:16:16: error: constexpr variable 'p' must be initialized by a constant expression
constexpr auto p = bar ();
               ^   ~~~~~~
/tmp/6.C:5:28: note: cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate' to allocate memory of type 'T'
  return static_cast<T *> (::operator new (sizeof (T)));
                           ^
/tmp/6.C:11:12: note: in call to 'foo()'
  auto a = foo <int> ();
           ^
/tmp/6.C:16:20: note: in call to 'bar()'
constexpr auto p = bar ();
                   ^
1 error generated.

Is it correct or not?  The allocation function itself is not constexpr, on the other side the wording says that the allocations should be elided in constexpr contexts.
Comment 13 Jonathan Wakely 2019-10-15 07:16:00 UTC
(In reply to Jakub Jelinek from comment #12)
> Well, not fully.  The paper has additions of constexpr keywords on the
> library side, but more importantly, we'll probably need some hacks in the
> compiler for the library side, but waiting for Jonathan with that.

Yes, I have the library parts coded up but not committed.

> My understanding is that right now (perhaps that might change) placement new
> should not be treated as constexpr function, but it needs to be in certain
> STL templates (at least std::construct_at, something else?)

The way EDG does it is not to allow placement new in constant expressions, but to intercept calls to std::construct_at and std::destroy_at and replace them with equivalent code that does the construction/destruction.

If allowing the body of std::construct_at to be valid is easier, that's fine too.


> Also, I was afraid that in std::allocator<T>::allocate, the cast would be
> reinterpret_cast, but apparently it isn't:
> 
> template <typename T>
> constexpr T *
> foo ()
> {
>   return static_cast<T *> (::operator new (sizeof (T)));
> }
> 
> constexpr int
> bar ()
> {
>   auto a = foo <int> ();
>   ::operator delete (a);
>   return 0;
> }
> 
> constexpr auto p = bar ();
> 
> (this doesn't actually use placement new, because that does and should ATM
> fail).  So, maybe it is just the placement new that needs to be handled.
> But, if one can cast the global replaceable allocator function result to
> anything in constexpr and our implementation triggers on the cast rather
> than on some spot coming from the new operator, shouldn't we use some new
> C++ expression kind or say an internal function as an explicit cast in new
> expression (placement or not) rather than any cast of the pointer?

Again, instead of trying to allow whatever the body of std::allocator::allocate does, EDG just intercepts call to std::allocator<T>::allocate and replaces it with something else that works for the constexpr case.

That means it doesn't matter what the body of std::allocator<T>::allocate does, and it doesn't matter whether operator new has been replaced by the program, because that code is never evaluated in constant expressions.

> Note, clang++ rejects the above testcase with
> /tmp/6.C:16:16: error: constexpr variable 'p' must be initialized by a
> constant expression
> constexpr auto p = bar ();
>                ^   ~~~~~~
> /tmp/6.C:5:28: note: cannot allocate untyped memory in a constant
> expression; use 'std::allocator<T>::allocate' to allocate memory of type 'T'
>   return static_cast<T *> (::operator new (sizeof (T)));
>                            ^
> /tmp/6.C:11:12: note: in call to 'foo()'
>   auto a = foo <int> ();
>            ^
> /tmp/6.C:16:20: note: in call to 'bar()'
> constexpr auto p = bar ();
>                    ^
> 1 error generated.
> 
> Is it correct or not?  The allocation function itself is not constexpr, on
> the other side the wording says that the allocations should be elided in
> constexpr contexts.

I don't think Clang implements this feature yet, so I'd expect it to fail.
Comment 14 Jakub Jelinek 2019-10-16 07:44:06 UTC
Another question is whether:
constexpr int
bar ()
{
  auto a = static_cast<int *> (::operator new (sizeof (int)));
  *a = 1;
  *a = *a + 2;
  int r = *a;
  ::operator delete (a);
  return r;
}

constexpr auto p = bar ();

is valid or not (i.e. when there is no placement new at all, just static_cast and dereferencing of the pointer.  Or if it is required to use std::allocator<int>::allocate for that, and in that case, does one need to use placement new or can it be dereferenced without it?
Comment 15 Jonathan Wakely 2019-10-16 09:16:49 UTC
That's not valid, because operator new is not a constexpr function.

You have to use a new-expression (that resolves to one of the standard operator new allocation functions), or std::allocator<T>::allocate, or std::allocator_traits<std::allocator<T>>::allocate.

The result of std::allocator<T>::allocate can't be dereferenced until you've constructed an object there (and attempting to do so in a constexpr function should be ill-formed).

This should work:

constexpr int
bar ()
{
  auto a = new int; // obtain storage for an int and begin its lifetime
  *a = 1;
  *a = *a + 2;
  int r = *a;
  delete a; // end lifetime and release storage
  return r;
}

constexpr auto p = bar ();

And this:

constexpr int
baz ()
{
  auto a = std::allocator<int>::allocate(1); // obtain storage for an int
  std::construct_at(a);                      // begin lifetime of an int
  *a = 1;
  *a = *a + 2;
  int r = *a;
  std::destroy_at(a);                        // end lifetime
  std::allocator<int>::deallocate(a, 1);     // release storage
  return r;
}

constexpr auto q = baz ();

And the equivalent using std::allocator_traits:

constexpr int
baz2 ()
{
  std::allocator<int> alloc;
  using A = std::allocator_traits<std::allocator<int>>;
  auto a = A::allocate(alloc, 1);            // obtain storage for an int
  A::construct(a);                           // calls std::construct_at
  *a = 1;
  *a = *a + 2;
  int r = *a;
  A::destroy(alloc, a);                      // calls std::destroy_at
  A::deallocate(alloc, a, 1);                // release storage
  return r;
}

constexpr auto q = baz2 ();
Comment 16 Jonathan Wakely 2019-10-16 09:18:01 UTC
I'll commit a patch to add std::construct_at today or tomorrow.
Comment 17 Jakub Jelinek 2019-10-17 10:10:27 UTC
Ok, so do I need to somehow mark the CALL_EXPR created from new/delete lowering and only treat calls to global replaceable allocator/deallocator functions specially if they either have this flag or are in allocate//deallocate method of std::allocator<T> template?  And similarly perhaps do something for the placement new in std::construct_at etc.?
Comment 18 Jonathan Wakely 2019-10-23 17:42:42 UTC
Author: redi
Date: Wed Oct 23 17:42:11 2019
New Revision: 277342

URL: https://gcc.gnu.org/viewcvs?rev=277342&root=gcc&view=rev
Log:
PR c++/91369 Implement P0784R7 changes to allocation and construction

This patch is the first part of library support for constexpr
std::vector and std::string. This only includes the changes to
std::allocator, std::allocator_traits, std::construct_at,
std::destroy_at, std::destroy and std::destroy_n.

std::allocator::allocate and std::allocator::deallocate need to be
added so that they can be intercepted by the compiler during constant
evaluation. Outside of constant evaluation those new member functions
just forward to the existing implementation in the base class.

	PR c++/91369 Implement P0784R7 changes to allocation and construction
	* include/bits/alloc_traits.h: Include <bits/stl_construct.h>.
	(allocator_traits::_S_allocate, allocator_traits::_S_construct)
	(allocator_traits::_S_destroy, allocator_traits::_S_max_size)
	(allocator_traits::_S_select, allocator_traits::allocate)
	(allocator_traits::deallocate, allocator_traits::construct)
	(allocator_traits::destroy, allocator_traits::max_size)
	(allocator_traits::select_on_container_copy_construction)
	(allocator_traits<allocator<T>>): Add constexpr specifier for C++20.
	(allocator_traits<allocator<T>>::construct): Use construct_at.
	(allocator_traits<allocator<T>>::destroy): Use destroy_at.
	(__alloc_on_copy, __alloc_on_move, __alloc_on_swap): Add constexpr
	specifier.
	(_Destroy(ForwardIterator, ForwardIterator, Alloc&))
	(_Destroy(ForwardIterator, ForwardIterator, allocator<T>&)): Move here
	from <bits/stl_construct.h>.
	* include/bits/allocator.h (allocator::~allocator): Remove for C++20.
	(allocator::allocate, allocate::deallocate): Define for C++20 and up.
	(operator==, operator!=): Add constexpr specifier for C++20.
	* include/bits/stl_construct.h: Don't include <ext/alloc_traits.h>.
	(destroy_at): For C++20 add constexpr specifier and support for
	destroying arrays.
	(construct_at): Define new function for C++20.
	(_Construct): Return result of placement new-expression. For C++11 and
	up add constexpr. For C++20 dispatch to std::construct_at during
	constant evaluation.
	(_Destroy(pointer)): Add constexpr specifier. For C++20 dispatch to
	std::destroy_at during constant evaluation.
	(_Destroy_aux::__destroy, _Destroy_n_aux::__destroy_n): Add constexpr
	specifier for C++20.
	(_Destroy(ForwardIterator, ForwardIterator))
	(_Destroy(ForwardIterator, Size)): Likewise. Do not elide trivial
	destructors during constant evaluation.
	(destroy, destroy_n): Add constexpr specifier for C++20.
	(_Destroy(ForwardIterator, ForwardIterator, Alloc&))
	(_Destroy(ForwardIterator, ForwardIterator, allocator<T>&)): Move to
	<bits/alloc_traits.h>, to remove dependency on allocators.
	* include/bits/stl_uninitialized.h: Include <ext/alloc_traits.h>.
	Include <bits/stl_pair.h> instead of <utility>.
	* include/ext/alloc_traits.h: Always include <bits/alloc_traits.h>.
	(__alloc_traits::construct, __alloc_traits::destroy)
	(__alloc_traits::_S_select_on_copy, __alloc_traits::_S_on_swap): Add
	constexpr specifier.
	* include/ext/malloc_allocator.h  (operator==, operator!=): Add
	constexpr specifier for C++20.
	* include/ext/new_allocator.h (operator==, operator!=): Likewise.
	* testsuite/20_util/headers/memory/synopsis.cc: Add constexpr.
	* testsuite/20_util/scoped_allocator/69293_neg.cc: Ignore additional
	errors due to constexpr function called after failed static_assert.
	* testsuite/20_util/specialized_algorithms/construct_at/1.cc: New test.
	* testsuite/23_containers/vector/cons/destructible_debug_neg.cc:
	Ignore additional errors due to constexpr function called after failed
	static_assert.
	* testsuite/23_containers/vector/cons/destructible_neg.cc: Likewise.

Added:
    trunk/libstdc++-v3/testsuite/20_util/specialized_algorithms/construct_at/
    trunk/libstdc++-v3/testsuite/20_util/specialized_algorithms/construct_at/1.cc
Modified:
    trunk/libstdc++-v3/ChangeLog
    trunk/libstdc++-v3/include/bits/alloc_traits.h
    trunk/libstdc++-v3/include/bits/allocator.h
    trunk/libstdc++-v3/include/bits/stl_construct.h
    trunk/libstdc++-v3/include/bits/stl_uninitialized.h
    trunk/libstdc++-v3/include/ext/alloc_traits.h
    trunk/libstdc++-v3/include/ext/malloc_allocator.h
    trunk/libstdc++-v3/include/ext/new_allocator.h
    trunk/libstdc++-v3/testsuite/20_util/headers/memory/synopsis.cc
    trunk/libstdc++-v3/testsuite/20_util/scoped_allocator/69293_neg.cc
    trunk/libstdc++-v3/testsuite/23_containers/vector/cons/destructible_debug_neg.cc
    trunk/libstdc++-v3/testsuite/23_containers/vector/cons/destructible_neg.cc
Comment 19 Jakub Jelinek 2019-10-30 21:55:43 UTC
Author: jakub
Date: Wed Oct 30 21:55:12 2019
New Revision: 277649

URL: https://gcc.gnu.org/viewcvs?rev=277649&root=gcc&view=rev
Log:
	PR c++/91369 - Implement P0784R7: constexpr new
	* constexpr.c (cxx_replaceable_global_alloc_fn): Don't return true
	for placement new.
	(cxx_placement_new_fn, is_std_construct_at): New functions.
	(cxx_eval_call_expression): Allow placement new in std::construct_at.
	(potential_constant_expression_1): Likewise.

	* g++.dg/cpp2a/constexpr-new5.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new5.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/constexpr.c
    trunk/gcc/testsuite/ChangeLog
Comment 20 Jakub Jelinek 2019-11-01 23:26:49 UTC
Author: jakub
Date: Fri Nov  1 23:26:17 2019
New Revision: 277732

URL: https://gcc.gnu.org/viewcvs?rev=277732&root=gcc&view=rev
Log:
	PR c++/91369 - Implement P0784R7: constexpr new
	* cp-tree.h (CALL_FROM_NEW_OR_DELETE_P): Define.
	* init.c (build_new_1, build_vec_delete_1, build_delete): Set
	CALL_FROM_NEW_OR_DELETE_P on the CALL_EXPR to allocator functions.
	* constexpr.c (is_std_allocator_allocate): Only allow
	global replaceable allocator functions if CALL_FROM_NEW_OR_DELETE_P
	or in std::allocate<T>::{,de}allocate.
	(potential_constant_expression_1): Likewise.

	* g++.dg/cpp2a/constexpr-new6.C: New test.
	* g++.dg/cpp2a/constexpr-new7.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C
    trunk/gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/constexpr.c
    trunk/gcc/cp/cp-tree.h
    trunk/gcc/cp/init.c
    trunk/gcc/testsuite/ChangeLog