Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Slight improvements, more normative text, updated examples, fixed typos. May still be unenforceable.
Page properties
hiddentrue

I am not convinced this is entirely sensible as a rule. The biggest issue is that this is unenforceable in a general case for C++. Consider uses of typeid where you want to get the type from an overloaded operator return type. Eg)

Code Block
struct S {
  // Assume these have side effects
  int f(char);
  double f(double);
};
 
void f() {
  S s;
  (void)typeid(s.f(1.0));
}

This code is valid, and somewhat reasonable, but disallowed. There's no way to statically determine whether those side effects are being relied upon or not.

Some expressions involve operands that are unevaluated. According to the C++ Standard, [expr], paragraph 8 [ISO/IEC 14882-2014]:

In some contexts, unevaluated operands appear. 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. — end note]

The following expressions do not evaluate their operands: sizeof()typeid()noexcept()decltype(), and declval().

Because an unevaluated operand in an expression is not evaluated, no side effects from that operand will be triggered. Reliance on those side effects will result in unexpected behavior. Do not rely on side effected effects in unevaluated operands.The following expressions do not evaluate their operands: sizeof()typeid()noexcept()decltype(), and declval()

Note that unevaluated expression operands are used in situations were the declaration of an object is required, but the definition of the object is not. For instance, in the example below, the function f() is overloaded, which relies on the unevaluated expression operand to select the desired overload, which is then used to determine the result of the sizeof() expression:

Code Block
languagecpp
int f(int);
double f(double);
 
size_t size = sizeof(f(0));
Such a use is not relying on the side effects of f(), and is conforming to this guideline.

Noncompliant Code Example (sizeof)

...

In this noncompliant code example, the call to g() is expression i++ is not evaluated within the decltype specifier:

Code Block
bgColor#FFcccc
languagecpp
langc
#include <iostream>

static int glob = 100;

int gvoid f() {
 return ++glob; }

void f() {int i = 0;
  decltype(g()i++) h = 12;
  std::cout << globi;
}

Consequently, the value of glob i remains 14. The function call syntax is used within decltype to distinguish between the return type of g() and the function type of g, but the call is never evaluated.0.

Compliant Solution (decltype)

In this compliant solution, g() i is called incremented outside of the decltype specifier, so that it is evaluated as desired:

Code Block
bgColor#ccccff
languagecpp
langc
#include <iostream>

static int glob = 100;

int gvoid f() {
 return ++glob; }

void f() {int i = 0;
  decltype(g()i) h = 12;
  g()++i;
  std::cout << globi;
}

Risk Assessment

If expressions that appear to produce side effects are an unevaluated operand, the results may be different than expected. Depending on how this result is used, it can lead to unintended program behavior.

...