Search code examples
c++sizeofprvalue

Why does C++ standard specify temporary object materialization for `sizeof` operator of prvalue?


The C++ ISO/IEC JTC1 SC22 WG21 N4860 standard says temporary objects are not created until necessary. It lists situations where temporary objects are materialized, including for sizeof operator:

[class.temporary]

The materialization of a temporary object is generally delayed as long as possible in order to avoid creating unnecessary temporary objects. [Note: Temporary objects are materialized:
(2.1) — when binding a reference to a prvalue (9.4.3, 7.6.1.3, 7.6.1.6, 7.6.1.8, 7.6.1.10, 7.6.3),
(2.2) — when performing member access on a class prvalue (7.6.1.4, 7.6.4),
(2.3) — when performing an array-to-pointer conversion or subscripting on an array prvalue (7.3.2, 7.6.1.1),
(2.4) — when initializing an object of type std::initializer_list<T> from a braced-init-list (9.4.4),
(2.5) — for certain unevaluated operands (7.6.1.7, 7.6.2.4),
(2.6) — when a prvalue that has type other than cv void appears as a discarded-value expression (7.2).

[expr.sizeof]

If the operand is a prvalue, the temporary materialization conversion (7.3.4) is applied.

Why do we have to create a temporary object at runtime just to evaluate prvalue like Foo{} (sizeof(Foo{})), when sizeof can be determined at compile time?


Solution

  • If the operand of sizeof is a prvalue, it is of course not actually materialized because the operand is unevaluated. However, saying that the temporary materialization conversion is part of the (unevaluated) hypothetical evaluation of the operand preserves the C++14 behaviour.

    In C++11 and C++14, sizeof(expr) could be used to cause a substitution failure if expr is an invalid expression (or if it's a type whose size you aren't allowed to ask, i.e., a function type or incomplete type). If expr is a prvalue, such as a function call foo() where foo returns T by value, then the expression foo() is considered to include, as its last step, the destruction of the temporary T object that is the return value. Therefore, a substitution failure occurs if T's destructor is inaccessible or deleted.

    In C++17, evaluating a prvalue doesn't automatically create a temporary object; the standard has to explicitly say that the temporary materialization conversion is performed, which creates a temporary object out of the prvalue. In order to preserve the C++14 behaviour that sizeof(expr) would cause a substitution failure in cases where expr is a prvalue whose type has a deleted or inaccessible destructor, it was necessary to say that the temporary materialization conversion occurs, which makes the destructor call part of the expression.

    (By the way, note that when decltype was introduced in C++11, it was specified as not requiring a prvalue operand of class type to have a non-deleted, accessible destructor, or even to be a complete type at all. So in C++17 and later, decltype is specified as not doing the temporary materialization conversion, which again preserves the previous behavior.)