Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overalignment of an existing type in c++17

Tags:

c++

c++17

Given an existing type T, it is possible to overalign it on the stack with the alignas() keyword:

  alignas(1024) T variable;

For dynamic allocation, we have a cumbersome syntax :

  T *variable = new (std::align_val_t(1024)) T;

However, there are two problems with this syntax:

  1. Microsoft's compiler emits error C2956 although the syntax is valid;
  2. There seems to be no corresponding operator for destruction and aligned delete.

A workaround seems would be the definition of a new type that encapsulates T:

alignas(1024)
struct AlignedType {
  T _;
};

Alignedtype variable = new AlignedType;  // Properly aligned in c++17
delete variable;                         // Suitable aligned deallocation function called in c++17

This workaround is messy, if T's constructor has parameters, we must add some syntaxic shenanigan to forward constructor arguments, and we need to access variable._ to get the real content.

Inheritance is a bit simpler, but if T is a fundamental type (like uint32_t), we can't use inheritance.

My question is as follow:

Does something like

using AlignedType = alignas(32) T;

is possible (the above does not compile) or is it just not possible to dynamically allocate an existing type using custom alignment without resorting to syntaxic complexities ?

like image 868
Étienne Avatar asked Oct 18 '25 20:10

Étienne


1 Answers

It is not possible to achieve the equivalent of your hypothetical:

using AlignedType = alignas(32) T;

There is no such thing as a type that is equivalent to another type but with different alignment. If the language allowed something like that to exist, imagine all the extra overload resolution rules we would have to add to the language.

The syntax

new (std::align_val_t(1024)) T;

should work. However, MSVC has a bug where it considers ::operator new(std::size_t, std::align_val_t) to be a "placement allocation function" rather than a "usual allocation function". This leads to the error you are seeing, where it complains that a placement allocation function matches a usual deallocation function.

(It seems that it is the standard's fault for not being clear: CWG2592. However, MSVC is to blame too; why did they choose to interpret the standard in a way that makes new (std::align_val_t(x)) T illegal?)

A workaround is to implement your own operator new and matching operator delete that delegate to the ones that you actually want to call:

struct alignment_tag {};

void* operator new(std::size_t size, alignment_tag, std::align_val_t alignment) {
    return operator new(size, alignment);
}

void operator delete(void* p, alignment_tag, std::align_val_t alignment) {
    operator delete(p, alignment);
}

Now you can do

T *variable = new (alignment_tag{}, std::align_val_t(1024)) T;

I personally would wrap this in something like:

template <class T>
struct AlignedDeleter {
    AlignedDeleter(std::align_val_t alignment) : alignment_(alignment) {}
    void operator()(T* ptr) const {
        ptr->~T();
        ::operator delete(ptr, alignment_);
    }
    std::align_val_t alignment_;
};

template <typename T>
using AlignedUniquePtr = std::unique_ptr<T, AlignedDeleter<T>>;

template <typename T, typename... Args>
AlignedUniquePtr<T> make_unique_aligned(std::align_val_t alignment, Args&&... args) {
    return AlignedUniquePtr<T>(new (alignment_tag{}, alignment) T(std::forward<Args>(args)...), AlignedDeleter<T>(alignment));
}

(You can always call .release() if you need to.)

like image 96
Brian Bi Avatar answered Oct 21 '25 12:10

Brian Bi