ISO/IEC JTC1 SC22 WG21
N4198
Richard Smith
richard@metafoo.co.uk
2014-10-06

Allow constant evaluation for all non-type template arguments

Motivation

C++ supports the following kinds of non-type template parameters and non-type template arguments (see [temp.param]/4 and [temp.arg.nontype]/1,5):

Parameter typeArgument syntax
Integral or enumeration Arbitrary constant expression
Pointer type Exact syntax &entity, array, function,
referring to a static storage duration object or function with linkage, or
arbitrary constant expression that evaluates to a null pointer
Reference type Exact syntax object, function,
referring to a static storage duration object or function with linkage
Pointer to member Exact syntax &X::y, or
arbitrary constant expression that evaluates to a null pointer-to-member
std::nullptr_t Arbitrary constant expression

The syntactic restrictions for pointers, references, and pointers to members are awkward and prevent reasonable refactorings. For instance:

template<int *p> struct A {};
int n;
A<&n> a; // ok

constexpr int *p() { return &n; }
A<p()> b; // error

The same refactorings work fine for integral types. They also work fine if the template argument happens to evaluate to a null pointer:

constexpr int *q() { return nullptr; }
A<q()> c; // ok!

The historical reason for the restriction was most likely that C++ previously did not have a sufficiently strong specification for constant expressions of pointer, reference, or pointer-to-member type. However, that is no longer the case. The status quo is that an implementation is required to evaluate such a template argument, but must then discard the result if it turns out to not be null.

In addition to the above, the restriction to entities with linkage is an artifact of exported templates, and could have been removed when the linkage restrictions on template type parameters were removed.

This paper proposes revising the above table as follows:

Parameter typeArgument syntax
Integral or enumeration Arbitrary constant expression
Pointer type Arbitrary constant expression that evaluates to the address of a complete object with static storage duration, a function, or a null pointer
Reference type Arbitrary constant expression that evaluates to a glvalue referring to a complete object with static storage duration or to a function.
Pointer to member Arbitrary constant expression
std::nullptr_t Arbitrary constant expression

The restriction that the constant expression must name a complete object is retained to avoid aliasing problems with pointers to subobjects:

struct A { int x, y; } a;
template<int*> struct Z;
using B = Z<&a.x + 1>;
using C = Z<&a.y>;
// Are B and C the same type?

Additionally, the (previously implicit) restriction against string literals, temporaries, and typeid expressions is retained, because those expressions are not required to produce the same object each time they are evaluated.

This paper also proposes to allow the conversions permitted in converted constant expressions for all non-type template arguments. In particular, this allows lvalue-to-rvalue conversions (supporting constexpr globals of the type of the template parameter) and user-defined conversions (supporting arguments of literal class type with appropriate user-defined constexpr conversion operators). Such conversions are already permitted for non-type template parameters of integral or enumeration type.

Proposed Wording

Change in 5.19 (expr.const) paragraph 3:

[...] A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, where the converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), array-to-pointer conversions (4.2), function-to-pointer conversions (4.3), qualification conversions (4.4), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4), and null pointer conversions (4.10) and null member pointer conversions (4.11) from std::nullptr_t, and where the reference binding (if any) binds directly. [ Note: such expressions may be used in new expressions (5.3.4), as case expressions (6.4.2), as enumerator initializers if the underlying type is fixed (7.2), as array bounds (8.3.4), and as integral or enumeration non-type template arguments (14.3). — end note ]
Drafting note: previously, a converted constant expression could only be of integral or enumeration type, so these conversions do not change any existing uses of the term.

Change in 14.3.2 (temp.arg.nontype) paragraph 1 and remove the bullets:

A template-argument for a non-type, non-template template-parameter shall be one of: For a non-type template-parameter of reference type or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of): [ Note: If the template-argument represents a set of overloaded functions (or a pointer or member pointer to such), the matching function is selected from the set (13.4). ]

Change in 14.3.2 (temp.arg.nontype) paragraph 2:

[ Note: A string literal (2.14.5) does not satisfy the requirements of any of these categories and thus is not an acceptable template-argument. String literals are not acceptable template-arguments. [ Example: ... ] ]

Change in 14.3.2 (temp.arg.nontype) paragraph 3:

[ Note: Addresses of array elements and names or addresses of non-static class members are not acceptable template-arguments. [ Example:
template<int* p> class X { };

int a[10];
struct S { int m; static int s; } s;

X<&a[2]> x3; // error: address of array element
X<&s.m> x4;  // error: address of non-static member
X<&s.s> x5;  // error: &S::s must be used OK: address of static member
X<&S::s> x6; // OK: address of static member
] ]

Change in 14.3.2 (temp.arg.nontype) paragraph 4:

[ Note: Temporaries, unnamed lvalues, and named lvalues with no linkage are not acceptable template-arguments when the corresponding template-parameter has reference type. [ Example: ... ] ]

Delete all of 14.3.2 (temp.arg.nontype) paragraph 5 other than its example and move it to before paragraph 2:

The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed. [ Example: ... ]

Change in 14.4 (temp.type) paragraph 1:

Two template-ids refer to the same class, function, or variable if [ Example: ... ]