Сколько существующего кода 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++.