C++17 new / delete с выравниванием - как указать выравнивание в delete?

В С++17 появилась возможность добавлять выравнивание в new. Однако код:

int main()
{
    size_t n = 5;
    double* Test = new(align_val_t(64)) double[n];
    delete[] Test;
    return 0;
};

Падает в исключение на операторе delete[] где-то в недрах ntdll и отладчик сообщает о разрушении кучи.

Подскажите, как указать delete[] что он имеет дело с выровненным объектом?


Дополнено:

int main()
{
    struct alignas(64) a_double
    {
        double d;
    };

    size_t n = 5;
    a_double*  Test = new a_double[n];
    std::cout << alignof(a_double) << std::endl;
    delete[] Test;
    return 0;
};

Вот такая конструкция на основе ms заметки заработала, но как то она выглядит стрёмно и монструозно - такой огород городить ради банального выравнивания.

Кроме того обратиться к Test[i] как к double уже не получится, нужно обращаться к полю Test[i].d, что совсем уже плохо читаемо.

А ещё, как пояснил @user7860670, эта конструкция выравнивает не сам массив, а каждый элемент массива, что не соответствует моей задаче.

Ещё уточнил в коде, что меня интересует динамический массив.


Дополнение 2:

Запустил clang напрямую (до этого я запускал его из под msvc студии, а он там в режиме совместимости) и всё перестало падать и с просто delete[] и с operator delete[](...). Теперь гадаю - всё дело в каких то особенностях msvc, которые clang воспроизводит в режиме совместимости (но без сообщения об ошибке как в самом msvc) или сейчас не падает потому что "звёзды так сошлись", но потом может упасть.

Upd: Выяснил, что не может упасть, а точно падает, просто при запуске без отладчика этих исключений не видно.


Ответы (2 шт):

Автор решения: user7860670

Синтаксис new(align_val_t(64)) double[5]; характеризуется дополнительной передачей placement аргументов в круглых скобочках. Аналогичной формы для оператора delete нет. При использовании placement синтаксиса пользователь должен сам вызывать деструктор объектов и (при необходимости) подходящую функцию подчистки памяти.

#include <new>
#include <cstddef>

int main()
{
    using item_t = double;
    ::std::align_val_t const align{64};
    ::std::size_t const count{5};
    item_t * const p_items{new(align) item_t [count]{}};
    for (auto p_item{p_items}; (p_items + count) != p_item; ++p_item)
    {
        p_item->~item_t();
    }
    ::operator delete [](static_cast<void *>(p_items), align);
    return 0;
}

online compiler

Visual Studio такой сценарий не поддерживает, выдавая ошибку C2956, которая должна быть скорее предупреждением.

→ Ссылка
Автор решения: mur

Здесь мне подсказали правильный ответ:

size_t n = 50;
auto Test = static_cast<double*>(::operator new[](n * sizeof(double), std::align_val_t(64)));
    for (size_t i = 0; i < n; i++)
        Test[i] = i;    // проверка что память реально доступна
::operator delete[](Test, std::align_val_t(64));

Эта конструкция работает и в clang и в msvc без исключений, без неопределённого поведения и без ошибки C2956.

Единственное - не следует забывать что здесь конструкторы/деструкторы элементов массива не вызываются автоматически. Если они нужны, то их надо вызывать вручную. Но для нужных мне тривиальных типов (int, float, double и т.п.) они и не нужны.

→ Ссылка