constexprSpecifier Reference (cppreference)static_castSyntax Reference (cppreference)- ISO/IEC 14882 C++ Standard Sections:
[expr.const],[expr.static.cast]
constexpr Cast From void* is a core language enhancement introduced in C++26 (via proposal P2738R1) that permits casting a void* pointer back to a typed object pointer (static_cast<T*>(void_ptr)) during compile-time constant evaluation. In C++20 and C++23, although constexpr dynamic memory allocations (new/delete) and placement construction (std::construct_at) were permitted, type-erased pointer conversions through void* remained strictly illegal inside constant expressions. Standardized in C++26, this feature enables type-erased memory allocators, custom memory arenas (std::pmr), object pools, and custom data containers to execute entirely during compilation.
C++20 introduced compile-time dynamic memory allocation (constexpr new/delete). However, low-level memory allocation architecture relies fundamentally on type-erased void* pointers:
// C++20/C++23 Compilation Failure
constexpr void* allocate_raw_bytes(std::size_t bytes) {
return ::operator new(bytes); // Returns void*
}
constexpr int test_allocation() {
void* raw_ptr = allocate_raw_bytes(sizeof(int));
// C++20/C++23 FATAL COMPILE ERROR:
// "cast from 'void*' is not allowed in a constant expression!"
int* typed_ptr = static_cast<int*>(raw_ptr);
::operator delete(raw_ptr);
return 42;
}- Conservative Pointer Restriction: Early
constexprspecification rules conservatively banned casting fromvoid*toT*to prevent raw memory type-punning in the compiler's constant expression interpreter. - Blockage of Custom Allocators and Arenas: Standard Library containers (
std::vector) functioned insideconstexprbecause compilers hardcoded compiler-intrinsic hooks specifically forstd::allocator<T>. However, custom user-defined allocators,std::pmr::memory_resourcepools, and type-erased memory containers could not function inconstexprbecause their interfaces operated onvoid*orstd::byte*buffers!
Solution: ISO C++26 proposal P2738R1 updated [expr.const] and [expr.static.cast], permitting static_cast<T*>(void_ptr) in constant expressions provided the target type
During compile-time constant evaluation, a static_cast<T*>(void_ptr) expression is well-formed if and only if:
-
Active Object Match: The pointer
void_ptrpoints to a valid, active object$U$ in compile-time memory. -
Type Compatibility Invariant: The target type
$T$ is identical to$U$ (ignoring cv-qualifiers) or is a pointer to a valid base or derived subobject of$U$ .
constexpr casting from void* does NOT permit unsafe type punning:
constexpr int bad_type_pun() {
float f = 3.14f;
void* void_ptr = &f;
// C++26 COMPILE ERROR: The compiler tracks that void_ptr points to 'float'!
// Attempting to cast to 'int*' violates constant evaluation type safety rules!
int* int_ptr = static_cast<int*>(void_ptr);
return *int_ptr;
}If the underlying object type in memory (
timeline
title Constexpr Cast From void* Evolution
C++20 : Introduced constexpr new / delete and std::construct_at
: Casting void* -> T* strictly PROHIBITED in constant expressions
: Custom allocators and std::pmr blocked from constexpr usage
C++26 : Introduced constexpr Cast From void* P2738R1
: static_cast<T*>void_ptr allowed in constexpr evaluation
: Enables custom constexpr memory allocators and arenas
---
## Comprehensive Code Examples
### Example 1: Type-Erased Custom Memory Allocator in `constexpr`
Demonstrates writing a custom type-erased bump-pointer allocator that executes at compile time in C++26.
```cpp
#include <iostream>
#include <cstddef>
#include <memory>
// Custom Type-Erased Bump-Pointer Allocator
class ConstexprArenaAllocator {
private:
alignas(alignof(std::max_align_t)) char m_buffer[1024]{};
std::size_t m_offset = 0;
public:
constexpr ConstexprArenaAllocator() = default;
// Type-erased allocation returning void*
constexpr void* allocate(std::size_t bytes) {
if (m_offset + bytes > 1024) throw "Arena Out of Memory!";
void* ptr = &m_buffer[m_offset];
m_offset += bytes;
return ptr;
}
};
struct Vector3D {
double x, y, z;
};
constexpr Vector3D create_arena_vector() {
ConstexprArenaAllocator arena;
// 1. Allocate raw type-erased memory buffer (returns void*)
void* raw_mem = arena.allocate(sizeof(Vector3D));
// 2. C++26: static_cast<Vector3D*> from void* is VALID in constexpr!
Vector3D* typed_ptr = static_cast<Vector3D*>(raw_mem);
// 3. Construct object via std::construct_at
std::construct_at(typed_ptr, 1.0, 2.0, 3.0);
return *typed_ptr;
}
int main() {
// Evaluated entirely at COMPILE TIME!
constexpr Vector3D vec = create_arena_vector();
static_assert(vec.x == 1.0 && vec.y == 2.0 && vec.z == 3.0,
"C++26 constexpr void* cast failed");
std::cout << "Compile-Time Arena Vector: (" << vec.x << ", " << vec.y << ", " << vec.z << ")\n";
return 0;
}
Demonstrates using C++26 void* casts to implement a type-erased optional wrapper in constant evaluation.
#include <iostream>
#include <memory>
#include <utility>
template <typename T>
class TypeErasedBox {
private:
alignas(T) char m_storage[sizeof(T)]{};
bool m_has_value = false;
public:
constexpr TypeErasedBox() = default;
template <typename... Args>
constexpr void emplace(Args&&... args) {
void* void_addr = m_storage;
// C++26: Casting void* address to T* inside constexpr!
T* typed_addr = static_cast<T*>(void_addr);
std::construct_at(typed_addr, std::forward<Args>(args)...);
m_has_value = true;
}
constexpr const T& value() const {
const void* void_addr = m_storage;
// C++26: Const void* cast
const T* typed_addr = static_cast<const T*>(void_addr);
return *typed_addr;
}
constexpr ~TypeErasedBox() {
if (m_has_value) {
T* typed_addr = static_cast<T*>(static_cast<void*>(m_storage));
std::destroy_at(typed_addr);
}
}
};
int main() {
constexpr auto res = []() constexpr {
TypeErasedBox<int> box;
box.emplace(42);
return box.value();
}();
static_assert(res == 42, "Compile-time type-erased box failed");
std::cout << "Type-erased constexpr box value: " << res << "\n";
return 0;
}-
Use
static_cast<T*>for Type-ErasedconstexprMemory Buffers:- In C++26 and later, write
static_cast<T*>(void_ptr)directly inside custom memory managers, object pools, and serialization layers.
- In C++26 and later, write
-
Type Safety Is Strictly Enforced at Compile Time:
- Do not attempt to use
void*casts to perform bitwise type punning (float$\to$ int). The compiler'sconstexprinterpreter tracks original object types in memory and will reject invalid type casts. Usestd::bit_castfor bit-level type reinterpretation.
- Do not attempt to use
-
Pair with
std::construct_atandstd::destroy_at:- Always use
std::construct_at(static_cast<T*>(void_ptr), args...)andstd::destroy_at(typed_ptr)to manage object lifetimes correctly inside placement memory buffers.
- Always use
- Related to:
- constexpr Specifier — Baseline compile-time function specifier.
- Relaxing Constraints On constexpr Functions — C++14 imperative
constexprrelaxations. - Polymorphic Memory Resources —
std::pmrallocators utilizingvoid*pointers. std::construct_at— Standard placement construction utility.
- Superseded by: None.
- Supersedes: Pre-C++26 compile-time errors when casting
void*pointers inside customconstexprallocators.
Back to C++26 section.