ISO/IEC JTC1 SC22 WG21

N3444=12-0134

Richard Smith

2012-09-21

This paper suggests the relaxation of a number of syntactic restrictions for
`constexpr` function definitions. Prior to
N3268,
the body of a constexpr function was required to be of the form

`{ return `*expression*; }

N3268 loosened up the rules to allow (7.1.5/3):

- null statements,
static_assert-declarations,typedefdeclarations andalias-declarations that do not define classes or enumerations,using-declarations,using-directives,- and exactly one return statement.

However, the syntactic constraints are still extremely restrictive, and the
lack of expressiveness is a common complaint directed at the `constexpr`
feature.

Consider this simple integer `pow` function:

*// Compute a to the power of n*
int pow(int a, int n) {
if (n < 0)
throw std::range_error("negative exponent for integer power");
if (n == 0)
return 1;
int sqrt = pow(a, n/2);
int result = sqrt * sqrt;
if (n % 2)
return result * a;
return result;
}

For this function, and many others like it, it is sometimes desirable to
allow compile time evaluation (for use in array bounds or enumerators). However,
under the current language rules, we cannot mark this function as
`constexpr` without completely rewriting it:

```
constexpr int pow_helper(int a, int n, int sqrt) {
return sqrt * sqrt * ((n % 2) ? a : 1);
}
```*// Compute a to the power of n*
constexpr int pow(int a, int n) {
return (n < 0) ? throw std::range_error("negative exponent for integer power") :
(n == 0) ? 1 : pow_helper(a, n, pow(a, n/2));
}

Such rewrites can be performed mechanically by the programmer to remove the
uses of local variable declarations and `if` statements from functions,
and thus allow them to be marked `constexpr`. However, this makes the
resulting code harder to read and understand, and forces an awkward coding style
on the programmer. There is no reason to require the programmer to go to this
effort.

Note that a helper function is required in the rewrite, to avoid computing
`sqrt` multiple times. This is necessary even for implementations which
aggressively cache `constexpr` evaluations, in order to give good
performance if the code is used in a context which is not evaluated at
translation time.

Allow arbitrary code in `constexpr` function definitions, with three
exceptions:

- Since modification of the values of variables is not permitted in core
constant expressions, iteration statements cannot beneficially be used in
`constexpr`functions, so they are disallowed. `switch`and`goto`statements are disallowed, to avoid the need to model complex control flow and possible infinite loops.- As with C++11, it must be possible for at least one control flow path through the function to produce a core constant expression.

This paper proposes applying the following changes. These choices are
largely independent. All references to `constexpr` functions here also apply to
`constexpr` constructors, unless otherwise indicated.

Declarations of variables with automatic storage duration will be permitted
within `constexpr` function definitions, so long as they have literal
type and, if a constructor is called to perform their initialization, that
constructor is a `constexpr` constructor. These can currently be
simulated using a helper function that binds the variables to parameters, and
the behavior of local variables would match the behavior of such parameters. In
particular, there is no requirement that the variables be `const`, but
any attempt to modify them would cause constant evaluation to fail (so the call
will be deferred to runtime).

Declarations of variables with static and thread storage duration will be
permitted within `constexpr` functions, with some restrictions. Firstly,
such a variable cannot have dynamic initialization. If it did, the initial value
of the variable could depend on the order in which the implementation chose to
evaluate `constexpr` function calls:

```
constexpr int first_val(int n) {
static int value = n;
```*// error: not constant initialization*
return value;
}
const int N = first_val(5);
int arr[first_val(10)];

Secondly, such a variable cannot have a non-trivial destructor. This allows
an implementation to evaluate a `constexpr` function call at will,
without any concern about whether such evaluation causes a side-effect at
program termination.

In all other respects, such `static` or `thread_local`
variables can be used within `constexpr` functions in the same ways that
they could be used if they were declared outside the function. In particular,
they do not need to be `constexpr` nor have a literal type if their value
is not used:

```
constexpr mutex &get_mutex(bool which) {
static mutex m1, m2;
```*// non-const, non-literal, ok*
if (which)
return m1;
else
return m2;
}

Type definitions, including `enum` and `class` definitions,
will be permitted without restriction within `constexpr` functions. There
appear to be no implementation or practical reasons which warrant their
restriction, and they can be useful for tracking local state of a
`constexpr` function, or as a comparator or similar object to be used as
an argument to a suitable `constexpr` algorithm.

```
constexpr algo &min_by_cost(algo *begin, algo *end) {
struct comparator {
constexpr bool operator()(const algo &a, const algo &b) const {
return a.cost() < b.cost();
}
};
return min_by(begin, end, comparator());
}
constexpr algo &algo_for_this_platform = min_by_cost(algos, algos + num_algos);
```

This example would also benefit from lambdas being permitted in
`constexpr` functions, but that is not proposed in this paper.

*if-statement*s will be permitted, as a more natural syntactic
alternative to the existing support for `? :` expressions. The rule that
a `constexpr` function must have exactly one `return` statement
will be relaxed to requiring *at least* one `return` statement, so
that each branch of an *if-statement* can return a value. For
`constexpr` constructors, any number of `return` statements will
be permitted.

If a control flow path is taken through a `constexpr` function which
does not reach a `return` statement or a `throw` expression, the
behavior of the program is undefined, so constant evaluation fails.

*compound-statement*s will be permitted, in order to allow
*if-statement*s to control complex computations involving variable
declarations.

*expression-statement*s will be permitted. Since no side-effects can
occur inside a constant expression, the only effect of an
*expression-statement* during function invocation substitution can be to
render the expression non-constant. However, that effect can be desirable in
some cases. For instance:

```
template<typename T, size_t N>
constexpr typename array<T, N>::const_reference array<T, N>::at(size_type n) const {
if (n >= N)
throw std::out_of_range("array::at");
return elems[n];
}
constexpr array<int, 3> arr = { 0, 1, 2 };
enum E { e = arr.at(5) };
```*// error at compile time*
int f() { return arr.at(5); } *// exception at runtime*

An arbitrary *expression-statement* is permitted, in order to allow
calls to functions performing checks and to allow assert-like constructs.
`void` also becomes a literal type, so that `constexpr` functions
which exist only to perform such checks may return `void`.

```
#define ASSERT(expr) \
(void)((expr) || assert_failed(#expr, __LINE__, __FILE__))
void assert_failed(...);
```*// not *`constexpr`
struct S {
std::array a<int, 100>;
size_t i;
constexpr void check_invariants() const {
ASSERT(i < a.size());
ASSERT(a[i] == 0);
}
S(std::array<int, 100> a_, size_t i_) : a(a_), i(i_) {
check_invariants();
}
};

In each case, the construct behaves identically within function invocation substitution as it would if the function were evaluated at runtime. The values of local variables are substituted into expressions prior to their evaluation, in the same manner that function argument values are substituted for references to the function parameters. If any evaluated expression is not a core constant expression after substitution, the function call is also not a core constant expression.

An implementation of an earlier revision of this proposal is available here.