14

According to [expr.const]/5.18:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

  • a new-expression ([expr.new]), unless the selected allocation function is a replaceable global allocation function ([new.delete.single], [new.delete.array]) and the allocated storage is deallocated within the evaluation of E;

A placement-new expression is not a constant expression.

To solve it, C++20 added std::construct_at. So why can't a placement-new expression be made a constant expression?

  • Probably because it would be non-trivial for compilers to implement. With the current rules you don't have to track memory separately from the objects created in it. The type stored at a memory location cannot be changed. If you allow placement-new in general, then you need to keep track of memory explicitly, as well as the different objects located in it at any given time. Verifying the object lifetime rules probably also becomes much more complicated. – user17732522 Sep 25 '22 at 09:19
  • Related/duplicate? https://stackoverflow.com/questions/41580022 – cigien Sep 25 '22 at 13:37
  • @cigien This question applies to C++20. Since `std::construct_at` can be a constant expression, why can't a placement-new expression be? – Blackteahamburger Oct 02 '22 at 12:27
  • To wager a guess: With `std::construct_at` as the more expressive and readable interface for placement new, the latter could be "morally deprecated". There would be additional burden to require placement new to be constexpr - depending on the compiler implementation; there is no point in requiring that change if it's not to be used anymore anyway. – peterchen Oct 07 '22 at 08:46
  • @peterchen It's not more readable. [std::construct_at provides no objective improvements over placement new. You have to state the type you're creating in both cases. The parameters to the constructor have to be provided in both cases. The pointer to the memory has to be provided in both cases.](https://stackoverflow.com/a/52971541/) – Blackteahamburger Oct 07 '22 at 08:50
  • 1
    @Blackteahamburger: there is no point in *us* discussing this. But from other changes, I believe that this is the opinion of the ISO commitee. – peterchen Oct 07 '22 at 09:56
  • @peterchen I don't believe that the commitee thinks so differently from us. – Blackteahamburger Oct 07 '22 at 11:23

3 Answers3

12

So I started a twitter thread on this a while ago here in response to the question:

Current implementations don't allow placement new() in constexpr context.

and I replied:

If we look at p0784r0: https://wg21.link/p0784r0

The paper mentioned two approaches:

  • in terms placement new
  • in terms of the standard allocator

by the time we get to p0784r3: https://wg21.link/p0784r3

placement new is dropped, IIUC there are implementation issues here.

The reply from Richard Smith was:

Full placement new support is hard for some implementations, so a restricted form is exposed through std::construct_at and allocator_traits::construct. For now in clang, we actually implement a quite general form of placement new, but only allow it inside namespace std.

to which I asked:

I am curious why this is difficult for some implementations or maybe easier why it is not difficult for clang.

and the reply to this was:

I am told that getting from the void* parameter to placement new back to a typed pointer is infeasible for some constexpr evaluators, presumably due to how they represent pointers. construct_at avoids this because it takes a T* not a void*.

Basically it comes down to some implementation don't have the machinery in place to do this in a general way using placement new. This document has some discussions on this but really raises more questions then answers but hints at some details:

Unfortunately this allows placement new to foul analyses which use address value propagation to refine aliasing based on knowing where the pointer points. In the example above, such an optimization may find that the address of bp->buf[0] to be associated with the object a. From that finding, the optimizer may then decide that accesses to the memory ought to be done through a glvalue which may access the stored value of a without invoking undefined behavior (§3.10 [basic.lval]). The initialization done via the new-expression is not such an access.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • @richard-smith pinging in case you have some details to add or correct. – Shafik Yaghmour Oct 12 '22 at 02:36
  • Yeah but we should just do it. `std::construct_at` is awful. – Barry Oct 14 '22 at 02:19
  • I don't think that's a good reason. If an implementation follows with the standard, then it should implement anything in any part of the standard. If an implementation does not follow the standard, then it may not implement anything in any part of the standard. Hide `construct_at` inside `std`... It doesn't seem to help at all. – Blackteahamburger Oct 15 '22 at 05:20
  • 3
    @ShafikYaghmour: You can’t `@` people not already involved (what with non-unique names and all). – Davis Herring Oct 16 '22 at 00:01
  • 1
    @Blackteahamburger: "*I don't think that's a good reason.*" It doesn't matter if you like it. You asked for the reason, and what we have here is as close to "the reason" as you're ever going to get. Also, the standard will always take into consideration ease of implementation. They try to avoid forcing implementers to make radical changes to their compilers in order to implement a feature. – Nicol Bolas Dec 06 '22 at 04:30
-1

It follows from:

During an evaluation of a constant expression, a call to an allocation function is always omitted. Only new-expressions that would otherwise result in a call to a replaceable global allocation function can be evaluated in constant expressions.

In short, placement new doesn't qualify here (hint: due to not using a replaceable allocation function).

darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    The question is really is why they couldn't just declare placement `new` to work. After all, `construct_at` and placement-`new` do (nearly) the exact same thing. They picked the functions they picked, but they could have picked different ones. – Nicol Bolas Oct 12 '22 at 02:15
  • 1
    I believe you should read the question more carefully. – Blackteahamburger Oct 15 '22 at 05:01
-2

Placement-new expressions are not constant expressions because they invoke undefined behavior if the object they create is not properly destroyed. This is because placement-new expressions bypass the normal object construction/destruction mechanisms, and the programmer is responsible for invoking the destructor manually. If you want to create an object in a constant expression, you can use std::construct_at to initialize the object at a given address:

int main(int argc, char* argv[]) {
    constexpr auto foo = []() {
        auto buffer = std::array<int, 4>{};
        return std::construct_at(buffer.data(), 1);
    };
    
    static_assert(foo() == 1, "!");
}
Sh_gosha
  • 111
  • 2
  • 2
    *Placement-new expressions are not constant expressions because they invoke undefined behavior if the object they create is not properly destroyed.* That's incorrect per [basic.life]/5: A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above. – Blackteahamburger Oct 15 '22 at 05:03
  • `std::construct_at` does the same thing as placement-new, except that it can be a constant expression. So, the programmer still need to call the destructor explicitly for an object of a class type with a non-trivial destructor. – Blackteahamburger Oct 15 '22 at 05:15