У меня была интересная дискуссия с парнем, умнее меня, и у меня остался открытый вопрос о выровненных хранилищах и тривиально копируемых / разрушаемых типах.
Рассмотрим следующий пример:
#include <type_traits>
#include <vector>
#include <cassert>
struct type {
using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
using fn_type = int(storage_type &);
template<typename T>
static int proto(storage_type &storage) {
static_assert(std::is_trivially_copyable_v<T>);
static_assert(std::is_trivially_destructible_v<T>);
return *reinterpret_cast<T *>(&storage);
}
std::aligned_storage_t<sizeof(void *), alignof(void *)> storage;
fn_type *fn;
bool weak;
};
int main() {
static_assert(std::is_trivially_copyable_v<type>);
static_assert(std::is_trivially_destructible_v<type>);
std::vector<type> vec;
type t1;
new (&t1.storage) char{'c'};
t1.fn = &type::proto<char>;
t1.weak = true;
vec.push_back(t1);
type t2;
new (&t2.storage) int{42};
t2.fn = &type::proto<int>;
t2.weak = false;
vec.push_back(t2);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto &t) { return t.weak; }), vec.end());
assert(vec.size() == 1);
assert(!vec[0].weak);
assert(vec[0].fn(vec[0].storage) == 42);
}
Это упрощенная версия кейса.Я действительно надеюсь, что не делал ошибок и не слишком упрощал.
Как видите, идея в том, что существует тип с именем type
(называть вещи сложно, вы знаете), имеющий три данныхчлены:
storage
, то есть набор байтов размером sizeof(void *)
fn
указатель на функцию типа int(storage_type &)
weak
бесполезный бул, используемый только для ознакомления с примером.
. Для создания новых экземпляров type
(см. Функцию main
) я помещаю значение (либо int
, либоchar
) в области хранения и правильной специализации шаблона статической функции proto
в fn
.
Позже, когда я хочу , вызовите fn
и получите целочисленное значениеон возвращается, я делаю что-то вроде этого:
int value = type_instance.fn(type_instance.storage);
Пока все хорошо.Несмотря на то, что это рискованно и подвержено ошибкам (но это пример, реального варианта использования нет), это работает .
Обратите внимание, что type
и все типы, которые я вставил вхранилище (int
и char
в примере) должно быть как тривиально копируемым, так и тривиально разрушаемым.Это также является основой обсуждения, которое у меня было.
Проблема (или лучше сомнение ) возникает, когда я помещаю экземпляры типов, например, в вектор (см. функцию main
) и решите удалить один из них из массива, чтобы некоторые другие перемещались, чтобы сохранить его упакованным.
В общем, я больше не уверен в том, что происходиткогда я хочу скопировать или переместить экземпляры type
и, если это UB или нет.
Я предположил, что было разрешено быть типами, помещаемыми в хранилище, тривиально копируемыми и тривиально разрушаемыми.С другой стороны, мне сказали, что это прямо не разрешено стандартом, и это можно считать доброкачественным UB , потому что практически все компиляторы фактически позволяют вам это делать (я могугарантируя это, кажется, что работа везде для некоторых определений работа ).
Итак, вопрос: это разрешено или UB и что я могу сделать, чтобыобойти проблему во втором случае?Кроме того, собирается ли C ++ 20 что-то изменить для этого?