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

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) правилами?

Ответы [ 2 ]

0 голосов
/ 14 декабря 2018
  • void - это тип с пустым доменом (у него нет возможных значений);
  • struct foo { } - это тип с непустым доменом (есть одно значение этого типа).

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

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

0 голосов
/ 07 ноября 2018

Есть предложение для этого, это p0146: Обычный 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?
  • Разве это не удаляет понятие «нет результата?»
  • Разве это не изменение значения пустоты?
...