Почему в некоторых реализациях std::option есть фиктивный член union?

И libstdc++ (GNU), и libc++ (LLVM) реализуют хранилище значений std::optional с использованием объединения, и оба они содержат фиктивный элемент.

Реализация GNU:

using _Stored_type = remove_const_t<_Tp>;
struct _Empty_byte { };
union {
    _Empty_byte _M_empty;
    _Stored_type _M_payload;
};

Реализация LLVM:

union
{
    char __null_state_;
    value_type __val_;
};

Мой вопрос: зачем нам эти члены _M_empty/__null_state_? Что-то не так с профсоюзом из одного члена?

Ответы

Ответ 1

Объединение одного члена создает все виды проблем при попытке быть конструируемым по умолчанию и быть совместимым с constexpr.

Рассмотрите этот код:

struct nontrivial {
    constexpr nontrivial(int o) : u{o} {}
    int u;
};

union storage {
    nontrivial nt;
};

struct optional {
    storage s;
};

constexpr auto run() -> int {
    optional o;
    return o.s.nt.u;
}

int main() {
    constexpr int t = run();
}

Это плохо сформировано, потому что optional имеет удаленный конструктор.

Тогда простым исправлением будет добавление конструктора, который не инициализирует член объединения:

union storage {
    constexpr storage() {} // standard says no
    nontrivial nt;
};

Но это не сработает. Профсоюзы Constexpr должны иметь хотя бы одного активного члена. Это не может быть пустой союз. Чтобы обойти это ограничение, добавляется фиктивный член. Это делает std::optional пригодным для использования в контексте constexpr.

(Спасибо @Barry!) Из [dcl.constexpr]/4 (выделено мной):

Определение конструктора constexpr, тело функции которого не равно = delete, должно дополнительно удовлетворять следующим требованиям:

  • если класс является объединением, имеющим альтернативные члены ([class.union]), то точно один из них должен быть инициализирован;
  • если класс является объединяющим классом, но не является объединением, для каждого из его анонимных членов объединения, имеющих вариантные члены, должен быть инициализирован ровно один из них;

  • для не делегирующего конструктора каждый конструктор, выбранный для инициализации нестатических членов данных и подобъектов базового класса, должен быть конструктором constexpr;

  • для делегирующего конструктора целевой конструктор должен быть конструктором constexpr.