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

Что такое необоснованные контексты на С++?

Один из примеров, который часто приходит на ум, - это:

sizeof, где он не оценивает выражение, а определяет размер по статическому типу. Например:

int func();
sizeof(func());

Это мой предел мышления, поэтому, если есть другие неоцененные контексты, то что это такое?

4b9b3361

Ответ 1

К счастью, стандарт имеет удобный список (§ 5 [expr] ¶ 8):

В некоторых контекстах отображаются неоцененные операнды (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). Неопределенный операнд не оценивается. Неопределенный операнд считается полным выражением.

Посмотрите на них подробно.

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

int foo();  // never defined anywhere

struct widget
{
  virtual ~widget();
  static widget& get_instance();  // never defined anywhere
};

typeid

§ 5.2.8 [expr.typeid] ¶ 3:

Когда typeid применяется к выражению, отличному от glvalue типа полиморфного класса, результат относится к объекту std::type_info, представляющему статический тип выражения. Преобразования Lvalue-to-rvalue (4.1), преобразования массива в указатель (4.2) и преобразования функции в указатель (4.3) не применяются к выражению. Если тип выражения является типом класса, класс должен быть полностью определен. Выражение - неоцененный операнд (Пункт 5).

Обратите внимание на выделенное исключение для полиморфных классов (a class с хотя бы одним членом virtual).

Следовательно, это нормально

typeid( foo() )

и дает объект std::type_info для int, а этот

typeid( widget::get_instance() )

не является и, вероятно, приведет к ошибке времени соединения. Он должен оценивать операнд, потому что динамический тип определяется поиском vptr во время выполнения.

<rant> Я нахожу довольно запутанным, что факт того, является ли статический тип операнда полиморфным, изменяет семантику оператора такими драматическими, но тонкими способами. </rant>

sizeof

§ 5.3.3 [expr.sizeof] ¶ 1:

Оператор sizeof дает количество байтов в представлении объекта его операнда. Операнд - это либо выражение, которое является неоцененным операндом (раздел 5), либо идентификатором типа в скобках. Оператор sizeof не должен применяться к выражению, которое имеет функцию или неполный тип, к типу перечисления, базовый тип которого не фиксирован до того, как все его перечисляемые были объявлены, в имя в скобках имени таких типов или значение gl, которое обозначает бит-поле.

Следующие

sizeof( foo() )

отлично и эквивалентно sizeof(int).

sizeof( widget::get_instance() )

тоже допускается. Обратите внимание, однако, что он эквивалентен sizeof(widget) и поэтому, вероятно, не очень полезен для полиморфного типа return.

noexcept

§ 5.3.7 [expr.unary.noexcept] ¶ 1:

Оператор noexcept определяет, может ли оценка его операнда, который является неоцененным операндом (раздел 5), исключить исключение (15.1).

Выражение

noexcept( foo() )

действителен и оценивается как false.

Вот более реалистичный пример, который также действителен.

void bar() noexcept(noexcept( widget::get_instance() ));

Обратите внимание, что только внутренний noexcept является оператором, а внешний - спецификатором.

decltype

§ 7.1.6.2 [dcl.type.simple] ¶ 4.4:

Операнд спецификатора decltype является неоцененным операндом (раздел 5).

Утверждение

decltype( foo() ) n = 42;

объявляет переменную n типа int и инициализирует ее значением 42.

auto baz() -> decltype( widget::get_instance() );

объявляет функцию baz, которая не принимает аргументов и return a widget&.

И что все есть (как на С++ 14).

Ответ 2

Стандартный термин - неоцененный операнд, и вы можете найти его в [expr]

В некоторых контекстах отображаются неоцененные операнды (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). Неопределенный операнд не оценивается. Неоценимый операнд считается полным выражением. [Примечание. В неопубликованном операнде член нестатического класса может быть назван (5.1), а присвоение имен объектам или функциям само по себе не требует предоставления определения (3.2). -end note]

  • 5.2.8 охватывает typeid
  • 5.3.3 охватывает sizeof
  • 5.3.7 охватывает noexcept
  • 7.1.6.2 охватывает простые спецификаторы типов, такие как auto и decltype и типы POD, такие как int, char, double и т.д.