Ответ 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).