Различия между decltype (void ()) и decltype (void{})

это продолжение вопроса: Что значит void() на decltype(void()) значит?.


decltype(void()) компилирует отлично и то, что void() означает, что в этом случае объясняется в вышеупомянутом вопросе (фактически в ответе).
С другой стороны, я заметил, что decltype(void{}) не компилируется.

в чем разница между ними (в контексте decltype по крайней мере)?
Почему второе выражение компилировать?


для полноты картины, следует минимальный (не)рабочий пример:

int main() {
    // this doesn't compile
    //decltype(void{}) *ptr = nullptr;
    // this compiles fine
    decltype(void()) *ptr = nullptr;
    (void)ptr;
}

2 ответов


void() интерпретируется как Type-id при использовании с sizeof.
void() интерпретируется как выражение при использовании decltype.

не думаю void{} действует в любом контексте. Это не является ни допустимым идентификатором типа, ни допустимым выражением.


(основываясь на обсуждении в комментариях к вопросу)

Примечание: я ссылался на C++17 или близко к ответу. C++14 работает так же, разница в тексте отмечается в конце ответа.

void() является особым исключением. Увидеть N4618 5.2.3 [expr.тип.conv], выделено мной:

1 A простой-тип-описатель (7.1.7.2) или параметр typename-описатель (14.6) с последующим скобками необязательный выражение-список или braced-init-list (инициализатор) создает значение указанного типа с учетом инициализатор. Если тип является заполнителем для выведенного типа класса, он заменяется возвращаемым типом функции, выбранной разрешением перегрузки для вычета шаблона класса (13.3.1.8) для остальной части этого раздела.

2, Если инициализатор заключен в скобки одиночное выражение, выражение преобразования типа эквивалентно (в определенности и если определено в значении) соответствующему выражению приведения (5.4). если тип (возможно, CV-qualified) void и инициализатор (), выражение является prvalue указанного типа, который не выполняет инициализации. в противном случае выражение является значением prvalue указанного типа, объект результата которого инициализируется напрямую (8.6) с инициализатором. Для выражения вида T () T не должен быть типом массива.

так void() является допустимым только потому, что он явно идентифицирован в [expr.тип.conv] / 2 as нет инициализации. void{} не соответствует этому исключению, поэтому он пытается быть прямой инициализации это.

  • 8.6/8.4 => ноль-инициализировать
  • теперь, 8.6/6 определяет ноль-инициализировать для:

    • скаляр
    • тип класса non-соединения
    • тип союза
    • тип массива
    • ссылка тип

    N4618 3.9 [basic.типов/9 определяет скаляр:

    арифметические типы(3.9.1), типы перечислений, типы указателей, указатель на типы членов (3.9.2), std:: nullptr_t, и CV-квалифицированные версии этих типов (3.9.3) в совокупности называются скалярные.

    N4618 3.9.1 [basic.fundamental] / 8 определяет арифметика типы:

    интегральные и плавающие типы совместно называются типы арифметических.

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

    помимо инициализация, void() и void{} будет работать таким же образом, производя prvalue выражение типа void. Даже если у вас не может быть объект-результат для неполного типа, а void is всегда неполной:

    N4618 3.9.1 [basic.fundamental] / 9 (Болд мой):

    A тип cv void является неполным типом, что не может быть завершено; такой тип имеет пустой набор ценности.

    decltype специально позволяет неполные типы:

    N4618 7.1.7.2 [decl.тип.простой]/5 (Болд мой):

    если операнд decltype-описатель является prvalue, преобразование временной материализации не применяется (4.4) и объект результата не предоставляется для prvalue. тип prvalue может быть неполным.


    В C++14, N4296 5.2.3 [expr.тип.conv] по-разному сформулированы. Балочные формы, почти машинально в parenthesised версия:

    A простой-тип-описатель (7.1.6.2) или параметр typename-описатель (14.6), за которым следует заключенный в скобки выражение-список создает значение указанного типа, заданное в списке выражений. Если список выражений является одним выражением, то выражение преобразования типов эквивалентно (в definedness, и если определено по смыслу) к соответствующему привести выражение (5.4). Если указанный тип является типом класса, то тип класса должен быть полным. Если в списке выражений указано более одного значения, типом должен быть класс с соответствующим объявленным конструктором(8.5, 12.1) и выражением T(x1, x2, ...) эквивалентно по сути декларации T t(x1, x2, ...); для некоторой изобретенной временной переменной t, результатом которой является значение t как prvalue.

    выражение T (), где T -simple-type-specifier или typename-specifier для типа объекта не-массива полного или (возможно CV-квалифицированного) void type, создает значение prvalue указанного типа, значение которого является значением, полученным путем инициализации значения (8.5) объекта типа T; инициализация не выполняется для случая void (). [Примечание: если T является неклассовым типом, который имеет квалификацию cv,cv-квалификаторы отбрасываются при определении типа результирующего значения prvalue (пункт 5). -конец Примечание]

    аналогично, a простой-тип-описатель или параметр typename-описатель затем braced-init-list создает временный объект указанного типа direct-list-initialized (8.5.4) с указанным braced-init-list, и его значением является этот временный объект как prvalue.

    эффект тот же для наших целей,изменить относится к P0135R1 Формулировка для гарантированного копирования elision через упрощенные категории значений, который удалил временное создание объекта из выражений. Вместо этого контекст выражения предоставляет объект результата для инициализации выражением, если контекст нуждается в нем.

    как отмечалось выше, decltype (в отличие от sizeof или typeid) не предоставляет объект-результат для выражения, поэтому void() работает, даже если он не может инициализировать результат объект.


    я чувствую, что исключение в N4618 5.2.3 [expr.тип.conv] должно быть применено к void{} тоже. Это означает, что рекомендации вокруг {} более сложный. См., например,ES.23: предпочитайте синтаксис {} инициализатора в основных руководящих принципах C++ , которые в настоящее время рекомендуют decltype(void{}) над decltype(void()). decltype(T{}) скорее всего, будет там, где этот кусает вас...