Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Placement new and delete in constexpr functions

Tags:

c++

c++20

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*)
like image 934
SherAndrei Avatar asked May 01 '21 14:05

SherAndrei


People also ask

Does constexpr allocate memory?

It means that you can allocate memory in a constexpr expression, but then the mem block must be released at the end of that expression. That way, the compiler can adequately track all the allocations, and I guess it's much easier to control and implement.

Can you modify constexpr?

A const int var can be dynamically set to a value at runtime and once it is set to that value, it can no longer be changed. A constexpr int var cannot be dynamically set at runtime, but rather, at compile time. And once it is set to that value, it can no longer be changed.

What functions can be constexpr?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.

Can a constexpr function throw?

Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.


1 Answers

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.

like image 135
T.C. Avatar answered Nov 09 '22 19:11

T.C.