Альтернатива Constexpr для размещения новых, чтобы иметь возможность оставлять объекты в памяти неинициализированными? - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь создать статический контейнер, который имеет основанную на стеке память и может содержать N экземпляров T. Очень похоже std::vector Я хочу, чтобы в настоящее время неиспользуемая память не содержала инициализированные элементы T. Это обычно решается с размещением нового, но это невозможно использовать в constexpr.

Использование союзов Я нашел трюк, что вы можете использовать союз для этого следующим образом:

template <typename value_type>
union container_storage_type
{
    struct empty{};
    constexpr container_storage_type(): uninitialized{}{}
    constexpr container_storage_type(value_type v): value(v){}
    constexpr void set(value_type v)
    {
        *this = literal_container_storage_type{v};
    }

    empty uninitialized;
    value_type value;
};

Это позволяет хранить элементы неинициализированными, устанавливая элемент empty, и это работает вокруг ограничения, что все члены в constexpr должны быть инициализированы.

Теперь проблема этого подхода заключается в том, что если value_type - это тип, который реализует operator=, то правило для профсоюзов гласит: :

Если объединение содержит нестатический элемент данных с нетривиальной специальной функцией-членом (конструктор копирования / перемещения, назначение копирования / перемещения или деструктор), эта функция по умолчанию удаляется в объединении и ее необходимо определить явно программистом.

Это означает, что для того, чтобы использовать этот трюк, мне нужно также реализовать operator= в объединении, но как это будет выглядеть?

constexpr container_storage_type& operator=(const container_storage_type& other)
{           
    value = other.value; //ATTEMPT #1
    //*this = container_storage_type(other.value);ATTEMPT #2

    return *this;
}

Попытка # 1: Это не представляется возможным, так как компилятор жалуется, что изменение активного члена объединения просто запрещено в константных выражениях. Попытка № 2: Это работает в методе set() из предыдущего фрагмента, так как он не изменяет активный элемент как таковой, но переназначает весь союз. Этот трюк, похоже, не может быть использован в операторе присваивания, поскольку он вызывает бесконечную рекурсию ...

Я что-то здесь упускаю или это действительно тупик для использования профсоюзов в качестве альтернативы размещения в constexpr?

Есть ли другие варианты размещения новых, которые я полностью пропустил?

https://godbolt.org/z/km0nTY Код, иллюстрирующий проблему

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

В C ++ 17 вы не можете.

текущие ограничения на то, что вы не можете делать в константных выражениях, включают:

  • выражение присваивания ([expr.ass]) или вызов оператора присваивания ([class.copy.assign]), который изменит активный член объединения;

  • a new-expression ;

Там действительно нет никакого способа обойти это.

<ч />

В C ++ 20 вы сможете, но, вероятно, не так, как вы думаете. Последнее ограничение будет ослаблено в C ++ 20 в результате P0784 до чего-то вроде:

  • a new-expression (8.3.4), если только выбранная функция распределения не является заменяемой функцией глобального распределения (21.6.2.1, 21.6.2.2);

То есть new T станет нормальным, но new (ptr) T все равно не будет разрешено. Как часть создания std::vector constexpr, мы должны иметь возможность управлять «сырой» памятью - но мы все еще не можем на самом деле управлять raw памятью. Все еще должно быть напечатано. Работа с необработанными байтами не сработает.

Но std::allocator не полностью не имеют дело с необработанными байтами. allocate(n) дает вам T*, а construct принимает T* в качестве местоположения и группу аргументов и создает новый объект в этом местоположении. В этот момент вам может быть интересно, чем это отличается от размещения новых - и единственное отличие состоит в том, что придерживаясь std::allocator, мы остаемся на земле T* - но размещение новых использует void*. Это различие оказывается критическим.

К сожалению, это имеет интересное следствие того, что ваша constexpr версия «выделяет» память (но она выделяет память компилятора, которая при необходимости повышается до статического хранилища - так что это делает то, что вы хотите) - но ваша чистая версия времени выполнения конечно, не хочет выделять память, ведь весь смысл в том, что это не так. Для этого вам придется использовать is_constant_evaluated() для переключения между распределением во время постоянной оценки и невыделением во время выполнения. Это по общему признанию не красиво, но это должно работать.

0 голосов
/ 31 октября 2018

Ваше хранилище может выглядеть примерно так:

// For trivial objects
using data_t = const array<remove_const_t<T>, Capacity>>;
alignas(alignof(T)) data_t data_{};
// For non-trivial objects
alignas(alignof(T)) aligned_storage_t<T> data_[Capacity]{};

Это позволит вам создать const массив не - const объектов. Тогда построение объектов будет выглядеть примерно так:

// Not real code, for trivial objects
data_[idx] = T(forward<Args>(args)...);
// For non-trivial objects
new (end()) T(forward<Args>(args)...);

Размещение новых обязательно здесь. Вы сможете иметь хранилище во время компиляции, но вы не сможете построить его во время компиляции для нетривиальных объектов.

Вам также нужно будет принять во внимание, является ли ваш контейнер нулевым размером и т. Д. Я предлагаю вам взглянуть на существующие реализации для векторов фиксированного размера, и есть даже некоторые предложения для constexpr векторов фиксированного размера, таких как p0843r1 .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...