Сколько существующего кода C++ сломалось бы, если void был фактически определен как "struct void {};"

void - причудливая бородавка в системе типов C++. Это неполный тип, который не может быть завершен, и у него есть все виды магических правил об ограниченных способах его использования:

Тип cv void - это неполный тип, который не может быть завершен; такой тип имеет пустой набор значений. Он используется в качестве типа возврата для функций, которые не возвращают значение. Любое выражение может быть явно преобразовано в тип cv void ([expr.cast]). Выражение типа сорта void должен быть использован только как выражение выражение, в качестве операнда выражения запятой, в качестве второго или третьего операнда ?: ([expr.cond]), как операнд typeid, noexcept или decltype, как выражение в операторе return для функции с возвращаемым типом cv void, или как операнд явного преобразования в тип cv void.

(N4778, [basic.fundamental] №9)

Помимо зудящего чувства ко всем этим странным правилам, из-за ограниченных способов его использования, он часто выступает в качестве болезненного особого случая при написании шаблонов; чаще всего кажется, что мы хотели бы, чтобы он вел себя больше как std::monostate.


Давайте на минуту представим, что вместо приведенной выше цитаты стандарт говорит о void что-то вроде

Это тип с определением, эквивалентным:

struct void {
    void()=default;
    template<typename T> explicit void(T &&) {}; // to allow cast to void
};

сохраняя магию void * - может иметь псевдоним любого объекта, указатели данных должны выжить при прохождении через void *.

Это:

  • должен охватывать существующие случаи использования void типа "правильные";
  • Возможно, можно было бы разрешить удаление приличного количества мусора, распространяемого по стандарту - например, [expr.cond] ¶2, вероятно, будет ненужным, а [stmt.return] будет значительно упрощен (при сохранении "исключения", которое return без выражения допустим для void и то, что функция "вытекает" из функции void эквивалентна return;);
  • должен быть таким же эффективным - оптимизация пустых классов в настоящее время поддерживается повсеместно;
  • быть по сути совместимым с современными ABI, и может быть еще компилятором специально для более старых.

Помимо совместимости, это обеспечит:

  • конструирование, копирование и перемещение этих пустых объектов, исключая особые случаи, обычно необходимые в шаблонах;
  • арифметика бонусного указателя на void *, действующая как для char *, который является распространенным расширением, весьма полезен при манипулировании двоичными буферами.

Теперь, помимо возможно измененных возвращаемых значений вещи <type_traits>, что это может нарушить в коде, который правильно сформирован в соответствии с текущими (C++ 17) правилами?

Ответ 1

Для этого есть предложение, это p0146: Regular Void

Ниже представлено определение структуры, аналогичное тому, что предлагается для void в этой статье. Фактическое определение не является типом класса, но оно служит довольно точным приближением того, что предлагается, и того, как разработчики могут думать о пустоте. Следует заметить, что это можно рассматривать как добавление функциональности к существующему типу void, во многом как добавление специальной функции-члена к любому другому существующему типу, у которого ее раньше не было, например, добавление конструктора перемещения к ранее не существующему типу. Копируемый тип. Это сравнение не совсем аналогично, потому что void в настоящее время не является обычным типом, но это разумное, неформальное описание, подробности которого будут описаны позже.

struct void {
  void() = default;
  void(const void&) = default;
  void& operator =(const void&) = default;

  template <class T>
  explicit constexpr void(T&&) noexcept {}
};

constexpr bool operator ==(void, void) noexcept { return true; }
constexpr bool operator !=(void, void) noexcept { return false; }
constexpr bool operator <(void, void) noexcept { return false; }
constexpr bool operator <=(void, void) noexcept { return true; }
constexpr bool operator >=(void, void) noexcept { return true; }
constexpr bool operator >(void, void) noexcept { return false; }

Это было хорошо принято в Оулу, июнь 2016 г. Отчет о поездке на встречу:

Обычный void - предложение удалить большинство случаев обработки void в специальном случае в языке, заставляя его вести себя как любой другой тип. Общая идея получила повышенный уровень поддержки с момента ее первоначальной презентации два раза назад, но некоторые детали все еще были спорными, в частности, возможность удалять указатели типа void *. Автору было предложено вернуться с пересмотренным предложением и, возможно, реализацией, которая поможет исключить неожиданные осложнения.

Я побеседовал с автором, и он подтвердил, что в основном он ожидает реализации, когда он планирует вернуть предложение.

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

  • Разве это предложение не вводит более специальный корпус для void?
  • Почему sizeof (void) не равен 0?
  • Это нарушает std :: enable_if?
  • На практике это нарушит совместимость ABI?
  • Разве constexpr_if не упрощает ветвление для пустот?
  • Разве не нелогично поддерживать некоторую операцию для void?
  • Разве это не удаляет понятие "нет результата?"
  • Разве это не изменение значения пустоты?

Ответ 2

  • void - это тип с пустым доменом (он не имеет возможных значений);
  • struct foo { } - это тип с непустым доменом (есть одно значение этого типа).

Другими словами, void является нижним типом, в то время как потенциальная struct void {} будет типом модуля.

Замена первого вторым существенно разрушает, ну и весь мир. Это не совсем отличается от решения, что 0 равно 1 в C++.