P1073R2, 2018-10-04
CWG


Richard Smith (richard@metafoo.co.uk)
Andrew Sutton (andrew.n.sutton@gmail.com)
Daveed Vandevoorde (daveed@edg.com)

Immediate functions

Revisions

Revision 0: Original proposal with wording.

Revision 1: Introduce constexpr! token and allow it in lambda expressions.

Revision 2: Replace constexpr! by consteval.

Introduction and motivation

The constexpr specifier applied to a function or member function indicates that a call to that function might be valid in a context requiring a constant-expression. It does not require that every such call be a constant-expression. Sometimes, however, we want to express that a function should always produce a constant when called (directly or indirectly), and a non-constant result should produce an error. Such a function is called an immediate function. We propose the decl-specifier consteval for that purpose:

consteval int sqr(int n) {
  return n*n;
}
constexpr int r = sqr(100);  // Okay.
int x = 100;
int r2 = sqr(x);  // Error: Call does not produce a constant.

There is one exception to the rule that a call to a consteval function must be a constant-expression: If the call appears directly in another consteval function, it need not be a constant-expression at that point (since a call to the enclosing function would ultimately have to produce a core constant expression, we do not have to worry about ever having to evaluate that nested call at run time). This permits composition of consteval functions:

consteval int sqrsqr(int n) {
  return sqr(sqr(n)); // Not a constant-expression at this  point,
}                     // but that's okay.

constexpr int dblsqr(int n) {
  return 2*sqr(n); // Error: Enclosing function is not
}                  // consteval.

Finally, as is the case with a bound member function (e.g., "x.f"), a consteval function can only be called; no pointer or reference to it can be formed:

using Int2Int = int(int);
Int2Int *pf = sqr;  // Error.

One consequence of this specification is that an immediate function never needs to be seen by a back end. That means that immediate functions are an alternative to many function-style macros. It also means that, unlike plain constexpr functions, consteval functions are unlikely to show up in symbolic debuggers. That runs into the broader issue that programmers currently have few tools to debug compile-time evaluation: This proposal does not aim at solving that problem in any way.

Source Locations

The "Library Fundamentals v. 2" TS contains a "magic" source_location class get to information similar to the __FILE__ and __LINE__ macros and the __func__ variable (see N4529 for the current draft, and N4129 for some design notes). Unfortunately, because the "value" of a source_location is frozen at the point source_location::current() is invoked, composing code making use of this magic class is tricky: Typically, a function wanting to track its point of invocation has to add a defaulted parameter as follows:


void my_log_function(char const *msg,
                     source_location src_loc
		                  = source_location::current()) {
  // ...
}
This idiom ensure that the value of the source_location::current() invocation is sampled where my_log_function is called instead of where it is defined.

Immediate (i.e., consteval) functions, however, create a clean separation between the compilation process and the constexpr evaluation process (see also P0992). Thus, we can make source_location::current() an immediate function, and wrap it as needed in other immediate functions: The value produced will correspond to the source location of the "root" immediate function call. For example:


consteval src_line() {
  return source_location::current().line();
}

void some_code() {
  std::cout << src_line() << '\n';  // This line number is output.
}

Variables

Given that consteval functions are a useful alternative to function-style macros, it is natural to wonder whether we could make consteval variables a useful alternative to non-function-style macros. We'd call such variables — which would never be seen by a back end — immediate variables:

consteval int RN = 42;
int LN = 42;

int f(int const&);

int r1 = f(LN);  // Direct reference binding.
int r2 = f(RN);  // Not a direct binding (temporary created).

Unfortunately, the exact specification of such a facility raises some difficult questions, some of which relate to the topic of enabling class types for nontype template parameters as proposed in P0732R1. This paper therefore does not pursue that extension at this time.

Notes

The desire for functionality like this comes up regularly. Sometimes, the motivation is to eliminate macros, and sometimes it just falls naturally out of the development process (e.g., helper functions meant to create nontype template argument values).

The impetus for the present paper, however, is the work being done by SG7 in the realm of compile-time reflection. There is now general agreement that future language support for reflection should use constexpr functions, but since "reflection functions" typically have to be evaluated at compile time, they will in fact likely be immediate functions.

The general principles of this facility were first discussed in Kona (2017) as part of the discussion of P0595r0. At the time, the following poll was recorded:

Provide a way to write a function that is guaranteed to be produce a constant expression, violations diagnosed at call site? (Direction poll for [immediate functions], ignoring syntax):
SF: 8 | F: 13 | N: 2 | A: 0 | SA: 0
(See http://wiki.edg.com/bin/view/Wg21kona2017/P0595R0. The syntax under consideration at that time was do constexpr instead of consteval.)

At the Rapperswil meeting in 2018, essentially this proposal was accepted by EWG except the specifier constexpr! was used instead of consteval. There was some discussion of syntax at the time, but the proposed spelling had reasonably strong support:

Proposal as presented for C++20
SF: 19 | F: 22 | N: 1 | A: 2 | SA: 0

Use "true constexpr" instead of "constexpr!"
However, reflector discussions later indicated an alternative would be preferred. This paper presents one such an alternative. Another that is also liked is constfold (if that happens to be preferred, a simple search-and-replace in the proposed wording below is all that is needed). Both consteval and constfold were searched for in Google's extensive codebase (which includes many open-source projects in addition to Google's own code) and no conflicts were found.

Acknowledgments

Thanks to Vitorrio Romeo for catching a missing wording change.

Wording changes

Add the keyword consteval to Table 5 ("Keywords") in [lex.key].

Add to the grammar of [dcl.spec] paragraph 1:

decl-specifier:
storage-class-specifier
defining-type-specifier
function-specifier
friend
typedef
constexpr
consteval
inline

Update [expr.prim.lambda] paragraph 3 as follows:

In the decl-specifier-seq of the lambda-declarator, each decl-specifier shall either be one of mutable, or constexpr, or consteval. [ Note: The trailing requires-clause is described in Clause 11. — end note ]

Append to [dcl.spec] paragraph 2:

The constexpr and consteval shall not both appear in a decl-specifiers-seq.

Update [dcl.constexpr] paragraph 1 as follows:

The constexprspecifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. The consteval specifier shall be applied only to the definition of a function or function template. A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable (10.1.6). If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the constexprthat same specifier. [ Note: An explicit specialization can differ from the template declaration with respect to the constexpr or consteval specifier. — end note ] [Note: Function parameters cannot be declared constexpr. — end note ]

Update [dcl.constexpr] paragraph 2 as follows:

A constexpr or consteval specifier used in the declaration of a function that is not a constructor declares that function to be a constexpr function. Similarly, a constexpr or consteval specifier used in a constructor declaration declares that constructor to be a constexpr constructor. A function or constructor declared with the consteval specifier is called an immediate function.

Update [dcl.constexpr] paragraph 8 as follows:

The constexpr or consteval specifier has no effect on the type of a constexpr function or a constexpr constructor.

Add a paragraph after paragraph 2 of [expr.prim.id]:

An id-expression that denotes an immediate function (_dcl.constexpr_) and is not the right hand expression of a class member access operation (_expr.ref_) can be used only as the (possibly parenthesized) postfix-expression of a function call (_expr.call_).

Add a paragraph at the end of [expr.call]:

A call to an immediate function (_dcl.constexpr_) that does not lexically appear in the function-body (_dcl.fct.def.general_) of an immediate function shall be a constant expression (_expr.const_).

In bullet (4.3.2) of [expr.ref] paragraph 4, strike

The expression can be used only as the left-hand operand of a member function call (12.2.1). [ Note: Any redundant set of parentheses surrounding the expression is ignored (8.4). — end note ]

and add to the end of bullet (4.3) of that same paragraph (after the two sub-bullets):

If E1.E2 refers to a nonstatic member function (_class.mfct.non-static_) or to an immediate function (_dcl.constexpr_) the expression can be used only as the left-hand operand of a member function call (12.2.1). [ Note: Any redundant set of parentheses surrounding the expression is ignored (8.4). — end note ]

Insert a bullet after the first bullet (8.6) of the definition of needed for constant evaluation in [expr.const] paragraph 8:

— a constexpr function ...
— an immediate function that is named by an expression (_basic.def.odr_), or
— a variable ...
and add an example at the end of the paragraph:
[ Example:

  struct N {
    N(N const&) = delete;
  };
  template<typename T> constexpr void bad_assert_copyable() { T t; T t2 = t; }
  using ineffective = decltype(bad_assert_copyable<N>());
    // bad_assert_copyable<N> is not needed for constant evaluation
    // (and thus not instantiated)
  template<typename T> consteval void assert_copyable() { T t; T t2 = t; }
  using check = decltype(assert_copyable<N>());
    // error: assert_copyable<N> is instantiated (because it is needed for constant
    // evaluation), but the attempt to copy t is ill-formed
	
— end example ]

Update [dcl.fct.def.default] paragraph 3 as follows:

An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. [...]

Update [temp.explicit] paragraph 1 as follows:

[...]An explicit instantiation of a function template, member function of a class template, or variable template shall not use the inline or, constexpr, or consteval specifiers.

Change [diff.cpp17.lex] paragraph 1 as follows:

...
Rationale: ... The consteval keyword is added to declare immediate functions (_dcl.constexpr_).
Effect on original feature: Valid ISO C++ 2017 code using concept, consteval, or requires as an identifier is not valid in this International Standard.