56

One example that frequently comes to mind is :

sizeof expression, where it doesn't evaluates the expression but determines the size by static type. For example :

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

This is my limit of thinking, so if there are other unevaluated contexts, then what are they?

Angelus Mortis
  • 1,534
  • 11
  • 25

2 Answers2

49

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 a std::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. The sizeof 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 returns a widget&.

And that's all there are (as of C++14).

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • 1
    there was a point in the comments that `auto` is also involved. And the other answer also mentions `auto` but it is not clear. Is that the case? – bolov Jan 29 '16 at 16:09
  • 2
    `auto` is introduced in the same section as `decltype` but I don't see how it would be an unevaluated context. It doesn't even take an operand. And the only time the word “unevaluated” appears in § 7.1.6.2 is in the paragraph quoted in my answer. But IANAL. – 5gon12eder Jan 29 '16 at 16:22
  • 1
    @5gon12eder Why doesn't `typeid(*singleton::get_instance())` work? – vsoftco Jan 29 '16 at 17:26
  • 2
    @vsoftco Because `singleton` is polymorphic, the expression generates run-time code to follow the `vptr`. This means an actual call to the undefined function `singleton::get_instance`. See the first highlighted sentence in the standard quote. Personally, I find this tricky difference kind of awkward. – 5gon12eder Jan 29 '16 at 17:29
  • @5gon12eder Thanks, I missed *"other than a glvalue of a polymorphic class type"*. – vsoftco Jan 29 '16 at 17:32
  • Might be nice to fill in the other cases to say whether `*singleton::get_instance()` is allowed. The `foo()` cases are pretty obvious :-) – Nemo Jan 29 '16 at 17:47
  • You can get even trickier with `auto` and `decltype()` if you use SFINAE. `template auto baz(T x) -> decltype(x.someFunc())` will cause that function to be chosen if `T::someFunc()` exists (while also providing the return type of `someFunc()`). This is primarily useful for a form of pseudo-specialization based on available member functions that wouldn't otherwise work due to how template substitution is evaluated (for example, when trying to provide a wrapper that will work with both `std::queue` and `std::stack`; `queue` uses `front` for the next-to-pop item while `stack` uses `top`) – JAB Jan 29 '16 at 19:18
  • Of course, you'll get an ambiguous overload error if you try to call the pseudo-specialized function with an object having both functions. – JAB Jan 29 '16 at 19:26
  • @JAB That's of course a more useful application than in my contrived examples but I wanted to keep the examples in the answer simple so people can understand them even if they don't know about more advanced features. – 5gon12eder Jan 29 '16 at 19:27
  • @5gon12eder I figured that people with enough knowledge of C++ to be interested in the term "unevaluated operand" would potentially be curious about more advanced uses of `auto`/`decltype()` as well. That's not even getting into `declval()`, which is _only_ usable in an unevaluated context. (There's also `std::result_of`, but the difference between that and `decltype` [is already covered on SO](https://stackoverflow.com/questions/2689709/difference-between-stdresult-of-and-decltype).) – JAB Jan 29 '16 at 19:36
  • As an aside, of those operators introducing an unevaluated context, only `sizeof` does not need parentheses around its argument (only needs it if applied to a type). – Deduplicator Jan 29 '16 at 19:48
  • Regarding the interesting behavior of `typeid`: If there had been `decltype` already when it was introduced sometime in the forgotten past, I'm sure they would have simply made it a completely standard evaluated context in all circumstances. – Deduplicator Jan 29 '16 at 20:08
  • 1
    @Deduplicator I was thinking about this too. But even in the past, they could have provided a `static_typeid` that never evaluates its operand and returns a `std::type_info` for the static type and a `dynamic_typeid` that always evaluates its operand and returns a `std::type_info` for the dynamic type which will always be the same for non-polymorphic types. – 5gon12eder Jan 29 '16 at 21:25
  • agree about the static vs. dynamic `typeid`. It is bad enough that `sizeof` also can be static vs. dynamic when variable length arrays are involved. This is a hindrance to generic programming, which is the most common scenario in which you actually use any of these operators. – Chris Beck Aug 10 '16 at 17:22
  • But why can decltype work correctly on (widget::get_instance()) without calling the function while typeid can't? – Ant Aug 13 '18 at 11:31
  • C++20 has [updated this list](https://timsong-cpp.github.io/cppwp/n4861/expr.context) further so that it now contains `requires`, `concept`, and parts of a `template`'s preamble. It may be worth updating this answer to contain the new contexts for anyone who stumbles on this answer this in the future. – Human-Compiler Nov 27 '20 at 19:29
9

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 and decltype and POD types like int, char, double etc.
NathanOliver
  • 171,901
  • 28
  • 288
  • 402