Подтвердить что ты не робот

Почему мы не можем объявить переменную типа void?

Я ищу официальное объяснение этого факта в Стандарте. Я нашел то, что говорит 3.9.1/9, и пытаюсь дать объяснение, используемое в этом разделе.

Раздел 3.9.1/9, N3797:

Тип void имеет пустой набор значений. Тип void - это неполный тип, который не может быть завершен. Он используется как возврат type для функций, которые не возвращают значение. Любое выражение может быть явно преобразованный в тип cv void (5.4). Выражение типа void должен использоваться только как оператор выражения (6.2), как операнд запятое выражение (5.18), как второй или третий операнд: (5.16), как операнд typeid, noexcept или decltype, как выражение в оператор возврата (6.6.3) для функции с возвратным типом void, или как операнд явного преобразования в тип cv void.

Я не понимаю, как это связано с тем, что тип void имеет пустой набор значений?

Предположим, что тип T имеет пустой набор значений. Почему компилятор выдает ошибку, когда встречается следующая строка:

extern T v; 

Мы можем декалировать переменную неполного типа следующим образом:

#include <iostream>
#include <cstring>

using namespace std;

struct Foo;

extern Foo f; //OK!

int main()
{
}

и он отлично работает

DEMO

Это не может быть сделано для типа void

#include <iostream>
#include <cstring>

using namespace std;

extern void f; //compile-time error

int main()
{
}

DEMO

4b9b3361

Ответ 1

Вы не можете объявить переменную типа void, поскольку переменные должны иметь тип объекта или быть ссылками, extern void f; не объявляет ссылку, а void не является типом объекта:

В разделе 3 [basic] говорится, что

Переменная вводится декларацией ссылки, отличной от нестатического элемента данных или объекта.

В разделе 3.9 [basic.types] говорится, что

Тип объекта - это (возможно, cv-квалифицированный) тип, который не является типом функции, а не ссылочным типом, а не типом void.

Ответ 2

"тип void является неполным типом

Вы не можете создавать переменные любого неполного типа

"..., который не может быть завершен"

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

Ответ 3

[edit] Нижеприведенный ответ дает достоверные наблюдения, но они противоречат друг другу. Поскольку они могут быть ценными, я не буду их удалять, но см. Ответ Ben Voight и комментарии там для более простого подхода.

Ваши замечания о объявлениях extern в частности разрешены в 7.1.1/8:

Имя объявленного, но undefined класса может использоваться в объявлении extern. Такое объявление может использоваться только способами, которые не требуют полного типа класса.

void не является объявленным, но undefined классом ", и нет другого исключения в 7.1.1, которое применяется.

Кроме того, 3,9/5 довольно явственно, что на самом деле это разрешено:

Класс, который был объявлен, но не определен, тип перечисления в определенных контекстах (7.2) или массив неизвестного размера или неполного типа элемента является не полностью определенным типом объекта. [45] Неполно определенные типы объектов и типы пустот являются неполными типами (3.9.1). Объекты не должны быть определены, чтобы иметь неполный тип.

Акцент мой. Эта часть стандарта весьма специфична в отношении различий между определениями и декларациями, поэтому путем пропуска указывается, что объявления разрешены.

Ответ 4

void - неполный тип - вы можете только объявлять указатели им и использовать их в сигнатурах функций. Очевидно, что extern Foo f; разрешено, потому что struct Foo может быть определен в другом модуле компиляции (и если это не ошибка будет обнаружена компоновщиком), но void не может быть "определен" (и компилятор знает это, конечно), поэтому void довольно особенный в этом случае.

Ответ 5

Если переменная имеет пустой набор значений, ее нельзя использовать ни для чего.

Вы не можете назначить ему, потому что нет возможных значений для назначения.

Вы не можете получить к нему доступ, потому что вы никогда не назначали ему, поэтому он имеет неопределенное значение.

Поскольку никаких возможных значений нет, размер переменной не существует.

void используется только как местозаполнитель в переменных местах. Он используется как тип возврата, чтобы указать, что функция не возвращает значение. Он использовался в C в списке аргументов, чтобы указать, что функция не принимает никаких аргументов (чтобы устранить двусмысленность из предварительной версии прототипа языка). И он использовал с объявлениями указателей для создания общих указателей, которые могут быть переведены на любой другой тип указателя. Нет такого аналогичного использования для него в объявлениях переменных.

Ответ 6

Поскольку C и С++ предполагают, что любые объекты могут сравниваться для идентификации, сравнивая их адреса, они должны гарантировать, что все объекты имеют фиксированный ненулевой размер. Если бы не это требование, на самом деле было много случаев, когда было бы полезно объявить объекты нулевого размера (например, в коде, который использует шаблоны, которые содержат поля, которые иногда могут быть полезны, а иногда и нет, или как средство принудительного добавления структуры к определенному выравниванию, требуя наличия элемента, требующего такого выравнивания]. Однако, как правило, типы нулевого размера будут несовместимы с тем фактом, что правило, указывающее, что каждый объект имеет уникальный адрес, не содержит исключений, которые позволяли бы иметь объекты нулевого размера, которые могли бы совместно использовать адрес.

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

Ответ 7

Ну, я действительно не вижу смысла в этом. Это будет здорово, если таким образом мы можем объявить переменную с неизвестным типом. Что-то вроде "void *" и массивы неизвестного размера. Представьте код следующим образом:

#include <iostream>
#include <cstring>

using namespace std;

extern void f;

int main()
{
    cout << (int &)f << endl; //cout 'f' as it was integer
}

struct {
    int a;
    double b;
} f{};

Теперь вы можете сделать что-то похожее с массивами:

#include <iostream>
#include <cstring>

using namespace std;

struct Foo;

extern int arr[];

int main()
{
    cout << arr[2] << endl;
}

int arr[4]{};

Жизнь пример.