Relaxing constraints on constexpr functions (original) (raw)

ISO/IEC JTC1 SC22 WG21
N3597
Richard Smith
2013-03-15

Overview

The features of a programming language are more useful and easy to understand if they are orthogonal, and can be naturally combined with one another.constexpr functions currently carry a number of restrictions which prevent their natural combination with many other language facilities (for instance, for loops, variable modifications, exceptions, and so on), and make them harder to write. Working around these restrictions often requires the sacrifice of expressiveness, and causes programmer frustration.

This paper explores the removal of most of the restrictions onconstexpr function definitions, in order to make them a simpler and more uniform extension of runtime C++ code. Idiomatic C++ code would be permitted within constexpr functions, usually with little or no modification from its non-constexpr form other than the addition of theconstexpr keyword.

Problem

Prior to N3268, the body of a constexpr function was required to be of the form

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

These restrictions on constexpr function definitions are still very severe, and the relaxation of the rules has resulted in them becoming harder to teach and to justify. Non-trivial constexpr functions tend to be complex and to use a style of coding which is unfamiliar to many, because code must be contorted to fit within the syntactic constraits, even if it already has a pure functional interface.

Consider std::bitset::all. Here is one possible implementation:

This code is simple and idiomatic, and can make use of other library components. However, if we wish to make this function constexpr, we must rewrite it:

This implementation suffers from several of the constexprrestrictions:

Alternatives

Discussion at Portland (October 2012) has identified that support for a simple for-loop is a minimum requirement for a satisfactory relaxation of the constexpr rules. This requirement can be attained in a number of ways:

The first option risks further fracturing the C++ language into aconstexpr piece and a "rest of C++" piece. The second and third options both require adding flow control and variable mutation to constant expression evaluation, and would make the restrictions on constexprfunctions seem more arbitary than they do today. Therefore we consider the final option in detail, and seek to identify an appropriate subset of C++ which improves simplicity of use without sacrificing simplicity of implementation much beyond that required to support a for-loop.

We must restrict our attention to a subset of C++ which it is reasonable to expect all major implementors to be able to support within constant expressions. Additionally, it is important to maintain a distinction between translation time and runtime, and to avoid permitting constructs which cannot be supported in the translation environment (for instance, there would be significant implementation problems in supporting a new at translation time and a corresponding delete at runtime).

Proposed solution

Promote constexpr to a largely unrestricted compile-time function evaluation mechanism. There is implementation experience of such a mechanism in the D programming language, where it is a popular feature (see the documentation for this feature). The programmer's model would become simple: constexpr allows their code to run during compilation.

Constant expressions

An expression is a constant expression if evaluating it following the rules of the C++ abstract machine succeeds without encountering

C++11's function invocation substitution is not needed in this model.constexpr function invocations are instead handled as normal by the C++ abstract machine.

Due to concerns over the simplicity of implementation, the evaluation of a_lambda-expression_, a throw-expression, and the creation of an object with a non-trivial destructor will continue to render an expression non-constant.

Object mutation within constant expressions

Objects created within a constant expression can be modified within the evalution of that constant expression (including the evaluation of anyconstexpr function calls it makes), until the evaluation of that constant expression ends, or the lifetime of the object ends, whichever happens sooner. They cannot be modified by later constant expression evaluations. Example:

This approach allows arbitrary variable mutations within an evaluation, while still preserving the essential property that constant expression evaluation is independent of the mutable global state of the program. Thus a constant expression evaluates to the same value no matter when it is evaluated, excepting when the value is unspecified (for instance, floating-point calculations can give different results and, with these changes, differing orders of evaluation can also give different results).

The rules for use of objects whose lifetime did not begin within the evaluation are unchanged: they can be read (but not modified) if either:

constexpr functions

As in C++11, the constexpr keyword is used to mark functions which the implementation is required to evaluate during translation, if they are used from a context where a constant expression is required. Any valid C++ code is permitted in constexpr functions, including the creation and modification of local variables, and almost all statements, with the restriction that it must be possible for a constexpr function to be used from within a constant expression. A constant expression may still have side-effects which are local to the evaluation and its result. For instance:

A handful of syntactic restrictions on constexpr functions are retained:

constexpr constructors

In any constexpr constructor, because the lifetime of the object under construction began during the evaluation of the surrounding constant expression (if any), the constructor and later parts of the evaluation are permitted to modify its fields. Example:

Block-scope static local variables

If a constexpr function contains a declaration of a variable of static or thread storage duration, some additional restrictions are required to prevent the evaluation from having side-effects.

In all other respects, such static or thread_localvariables 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:

Possible additional features

Some of the remaining restrictions on constexpr functions and constant expression evaluation could be relaxed, if the value of the language feature within a constant expression is thought to be sufficient to justify the implementation cost:

constexpr destructors

In most cases, in order to create an object of a type T in a constant expression, the destruction of T must be trivial. However, non-trivial destructors are an important component of modern C++, partly due to widespread usage of the RAII idiom, which is also applicable in constexprevaluations. Non-trivial destructors could be supported in constant expressions, as follows:

However, no compelling use cases are known for such a feature, and there would be a non-trivial implementation cost ensuring that destructors are run at the right times.

Lambdas

N2859notes that severe implementation difficulties would arise if lambdas were permitted in contexts which require their contents to be part of a mangled name, and the prohibition on lambdas in constant expressions form part of the resolution to those difficulties. Also, concerns have been raised about the implementation cost of permitting lambdas in constant expressions, so they are not proposed here.

Exceptions

Throwing and catching exceptions within constant expression evalutations is possible to support, but we do not know of a compelling use case for it, so it is not proposed.

Variadic functions

It would be possible to support C-style variadic functions and the va_argmacro within constexpr functions, but this is thought to have little value in the presence of variadic function templates, so is not proposed.

Acknowledgements

The author wishes to thank Bjarne Stroustrup and Gabriel Dos Reis for their encouragement and insights on this proposal, and is also grateful to Lawrence Crowl, Jeffrey Yasskin, Dean Michael Berris, and Geoffrey Romer for their comments and corrections on earlier drafts of this paper.