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*)
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.
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.
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.
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.
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], whereT
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 tostd::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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With