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

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

Тип cv void — это неполный тип, который не может быть завершен; такой тип имеет пустой набор значений. Он используется в качестве возвращаемого типа для функций, которые не возвращают значение. Любое выражение можно явно преобразовать в тип cv void ([expr.cast]). Выражение типа cv 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) правилами?


person Matteo Italia    schedule 07.11.2018    source источник
comment
Вы слышали об Regular Void?   -  person Rakete1111    schedule 07.11.2018
comment
@ Rakete1111: ох, это действительно кажется очевидным решением! Спасибо, я посмотрю!   -  person Matteo Italia    schedule 07.11.2018
comment
@ Rakete1111 Массивы void выглядят как убийственная функция.   -  person user7860670    schedule 07.11.2018
comment
Стандарт требует, чтобы sizeof(void*) == sizeof(char*). Тот факт, что указатели на неполные типы существуют, означает, что все указатели структур пахнут одинаково. Поскольку вы предлагаете, чтобы void была структурой, это означает, что sizeof(void*) == sizeof(struct Foo*) означает, что sizeof(char*) == sizeof(struct Foo*), что усложняет жизнь системам без байтовой адресации, таким как TOPS-20.   -  person Raymond Chen    schedule 10.11.2018
comment
@RaymondChen: проблема, которую вы подчеркиваете, заключается в том, что, учитывая, что sizeof(void *) >= sizeof(any other data object), это сделало бы struct указатели слишком большими без уважительной причины?   -  person Matteo Italia    schedule 10.11.2018
comment
@MatteoItalia Это заставит sizeof(struct Foo*) иметь тот же размер, что и sizeof(void*), и потребует реализации для поддержки структур с выравниванием по байтам (поскольку void теперь будет структурой с выравниванием по байтам). Для систем без байтовой адресации это увеличивает использование памяти (указатели структур становятся больше) и размер кода (разыменование указателей теперь намного сложнее).   -  person Raymond Chen    schedule 10.11.2018
comment
@RaymondChen: я понимаю проблему, но не уверен, что это потребует sizeof(struct Foo*) == sizeof(void *). Тот факт, что sizeof(struct Foo*) == sizeof(struct Bar*), хотя это и не указано явно, AFAIK, исходит из того факта, что любой указатель на struct должен быть полным типом, даже если Foo и Bar являются неполными типами (IOW, он не должен зависеть от фактического определения Foo и Bar); это не будет применяться к void, так как это будет встроенный тип, то есть всегда уже объявленный, как сейчас.   -  person Matteo Italia    schedule 10.11.2018


Ответы (2)


Для этого есть предложение, оно p0146: Обычная пустота

Ниже представлено определение структуры, аналогичное тому, что предлагается для пустоты в этой статье. Фактическое определение не является типом класса, но оно служит довольно точным приближением к тому, что предлагается и как разработчики могут думать о пустоте. Следует отметить, что это можно рассматривать как добавление функциональности к существующему типу 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*. Автору было предложено вернуться с исправленным предложением и, возможно, реализацией, чтобы исключить неожиданные осложнения.

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

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

  • Разве это предложение не вводит более специальный регистр для пустоты?
  • Почему sizeof(void) не равен 0?
  • Нарушает ли это std::enable_if?
  • На практике нарушит ли это совместимость с ABI?
  • Разве constexpr_if не упрощает ветвление для void?
  • Разве не нелогично поддерживать некоторую операцию для пустоты?
  • Разве это не устраняет понятие «Нет результата?»
  • Разве это не изменение значения пустоты?
person Shafik Yaghmour    schedule 07.11.2018
comment
Было бы сложнее отличить указатель на что-то неизвестное (тип-стирание в конечном итоге) и void-объект/массив из voids. Интересно, как будет происходить неявное преобразование указателя данных в void*. Иногда отсутствие регулярности на самом деле является функцией безопасности. - person Deduplicator; 08.11.2018
comment
Является ли void базовым классом любого другого класса? И каждого скалярного типа? Бонусный вопрос. Является ли void виртуальной базой (во избежание неоднозначных преобразований)? Попытка приспособить полную непротиворечивую структуру (или систему типов) к несогласованности набора специальных правил — сложная задача. - person curiousguy; 10.11.2018
comment
@curiousguy: прочитайте предложение: по сути, это то, что я изложил в вопросе; void становится обычным, инстанцируемым типом; действующие специальные правила в отношении void * остаются в силе; базовые классы и тому подобное никогда не вступают в игру. - person Matteo Italia; 10.11.2018
comment
@MatteoItalia void не может стать на 100% обычным, потому что все еще есть код, использующий (void) для пустого списка аргументов. - person curiousguy; 15.12.2018

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

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

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

person einpoklum    schedule 14.12.2018
comment
Аналогия не работает, это больше похоже на замену bool на int - вы добавляете возможные значения, но если вы их не используете, никакого вреда не будет. - person Matteo Italia; 14.12.2018
comment
Действительно, вы не можете сделать void x; return x; или даже (void());. Но тогда int f(void); также не является объявлением функции, принимающей одно нижнее значение void. В C/C++ нет ничего обычного. Вот почему так сложно правильно делать дженерики. - person curiousguy; 15.12.2018
comment
@curiousguy: C ++ действительно несколько сломан в этом смысле. И хотя это можно исправить, правильное исправление подобных вещей означает отказ от обратной совместимости в пользу того меньшего и более ясного языка, изо всех сил пытающегося выбраться, о котором упоминал Бьярн. Во всяком случае, это мое мнение. - person einpoklum; 15.12.2018
comment
void не нижний тип. [[noreturn]] void — нижний «тип». - person user3840170; 28.03.2021
comment
@ user3840170: [[noreturn]] — это атрибут функции, а не типа. Не существует такого понятия, как тип [[noreturn]] void. - person einpoklum; 28.03.2021
comment
Спасибо за объяснение пугающих кавычек вокруг «шрифта». - person user3840170; 28.03.2021