SherAndrei
SherAndrei

Reputation: 627

Placement new and delete in constexpr functions

Let's imagine we have some class

struct Foo {
    constexpr Foo(int& x) : x_(x) { x_++; }
    constexpr ~Foo() noexcept     { x_++; }

    int& x_;
};

In C++20 with g++-10 -std=c++20 we can have a function like this:

constexpr int do_foo_1() {
    int x = 0;
    Foo* p = new Foo(x);
    delete p;
    return x;
}

int main() {
    static_assert(do_foo_1() == 2);
}

Let's try to divide operator new into memory allocation and inplace construction. So new function looks like this:

constexpr int do_foo_2() {
    int x = 0;
    Foo* p = static_cast<Foo*>(::operator new(sizeof(Foo)));
    p = ::new ((void*) p) Foo(x);
    p->~Foo();
    ::operator delete(p);
    return x;
}

But now there are two errors: our memory allocation new and placement new are not constexpr!

error: call to non-‘constexpr’ function ‘void* operator new(std::size_t)’
error: call to non-‘constexpr’ function ‘void* operator new(std::size_t, void*)’

So let's try to work around these errors. With <memory> we can have code like this:

constexpr int do_foo_3() {
    std::allocator<Foo> alc;
    int x = 0;
    Foo* p = alc.allocate(1);
    p = std::construct_at(p, x);
    std::destroy_at(p);
    alc.deallocate(p, 1);
    return x;
}

int main() {
    static_assert(do_foo_3() == 2);
}

Question

What is the difference between my usage and usage of these operators in standard library? Isn't the same thing happens in std::construct_at and std::allocator<Foo>::allocate under the hood?

Note

I tried to replicate std::construct_at by simply copying it implementation from <stl_construct.h> but I got same error:

error: ‘constexpr decltype (...) my_construct_at(_Tp*, _Args&& ...) [...]’ called in a constant expression
error: call to non-‘constexpr’ function ‘void* operator new(std::size_t, void*)

Upvotes: 7

Views: 1341

Answers (1)

T.C.
T.C.

Reputation: 137425

What is the difference between my usage and usage of these operators in standard library?

Your usage is not called std::allocator<T>::allocate or std::construct_at.

These particular functions - along with a few others - are specifically granted exceptions to the normal rule:

For the purposes of determining whether an expression E is a core constant expression, the evaluation of a call to a member function of std​::​allocator<T> as defined in [allocator.members], where T is a literal type, does not disqualify E from being a core constant expression, even if the actual evaluation of such a call would otherwise fail the requirements for a core constant expression. Similarly, the evaluation of a call to std​::​destroy_­at, std​::​ranges​::​destroy_­at, std​::​construct_­at, or std​::​ranges​::​construct_­at does not disqualify E from being a core constant expression unless: [...]

As for plain new expressions, during constant evaluation it never calls ::operator new:

During an evaluation of a constant expression, a call to an allocation function is always omitted.

Upvotes: 10

Related Questions