What are unevaluated contexts in C++?
Fortunately, the standard has a handy list of those (§ 5 [expr] ¶ 8):
In some contexts, unevaluated operands appear (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). An unevaluated operand is not evaluated. An unevaluated operand is considered a full-expression.
Let's look at these in detail.
I will use the following declarations in my examples. The declared functions are never defined anywhere so if a call to them appears in an evaluated context, the program is ill-formed and we will get a link-time error. Calling them in an unevaluated context is fine, however.
int foo(); // never defined anywhere
struct widget
{
virtual ~widget();
static widget& get_instance(); // never defined anywhere
};
typeid
§ 5.2.8 [expr.typeid] ¶ 3:
When
typeid
is applied to an expression other than a glvalue of a polymorphic class type, the result refers to astd::type_info
object representing the static type of the expression. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions are not applied to the expression. If the type of the expression is a class type, the class shall be completely-defined. The expression is an unevaluated operand (Clause 5).
Note the emphasized exception for polymorphic classes (a class
with at least one virtual
member).
Therefore, this is okay
typeid( foo() )
and yields a std::type_info
object for int
while this
typeid( widget::get_instance() )
is not and will probably produce a link-time error. It has to evaluate the operand because the dynamic type is determined by looking up the vptr
at run-time.
<rant>I find it quite confusing that the fact whether or not the static type of the operand is polymorphic changes the semantics of the operator in such dramatic, yet subtle, ways.</rant>
sizeof
§ 5.3.3 [expr.sizeof] ¶ 1:
The
sizeof
operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 5), or a parenthesized type-id. Thesizeof
operator shall not be applied to an expression that has function or incomplete type, to an enumeration type whose underlying type is not fixed before all its enumerators have been declared, to the parenthesized name of such types, or to a glvalue that designates a bit-field.
The following
sizeof( foo() )
is perfectly fine and equivalent to sizeof(int)
.
sizeof( widget::get_instance() )
is allowed too. Note, however, that it is equivalent to sizeof(widget)
and therefore probably not very useful on a polymorphic return
type.
noexcept
§ 5.3.7 [expr.unary.noexcept] ¶ 1:
The
noexcept
operator determines whether the evaluation of its operand, which is an unevaluated operand (Clause 5), can throw an exception (15.1).
The expression
noexcept( foo() )
is valid and evaluates to false
.
Here is a more realistic example that is also valid.
void bar() noexcept(noexcept( widget::get_instance() ));
Note that only the inner noexcept
is the operator while the outer is the specifier.
decltype
§ 7.1.6.2 [dcl.type.simple] ¶ 4.4:
The operand of the
decltype
specifier is an unevaluated operand (Clause 5).
The statement
decltype( foo() ) n = 42;
declares a variable n
of type int
and initializes it with the value 42.
auto baz() -> decltype( widget::get_instance() );
declares a function baz
that takes no arguments and return
s a widget&
.
And that's all there are (as of C++14).
The standard term is an unevaluated operand and you can find it in [expr]
In some contexts, unevaluated operands appear (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). An unevaluated operand is not evaluated. An unevaluated operand is considered a full-expression. [ Note: In an unevaluated operand, a non-static class member may be named (5.1) and naming of objects or functions does not, by itself, require that a definition be provided (3.2). —end note ]
- 5.2.8 covers
typeid
- 5.3.3 covers
sizeof
- 5.3.7 covers
noexcept
- 7.1.6.2 covers simple type specifiers such as
auto
anddecltype
and POD types likeint
,char
,double
etc.