c++pointerstemplateslanguage-lawyernon-type-template-parameter

Why using the address of the first element of the array as template non-type argument is possible in c++ 17


template<typename T, T nontype_param>
class C {};

int a[10];
C<int *, &a[0]> err3;

The code above will throw an error when the standard of c++ is lower than 17, while it will be ok when the standard is 17. Could someone explain the reasons to me?

According the information on the cppreference:

The only exceptions are that non-type template parameters of reference or pointer type and non-static data members of reference or pointer type in a non-type template parameter of class type and its subobjects(since C++20) cannot refer to/be the address of

or a subobject (including non-static class member, base subobject, or array element) of one of the above(since C++20).

(The words in cppreference are formatted, and I am unsure how to correctly paste it here in markdown. If you are confused by my reference, please click the link and find the section to read. )

I think using the address of the first element of the array as template non-type argument should not pass even in c++ 17.


Solution

  • tldr;

    The answer depends on which standard c++ version you're using as explained below. In C++20 the program is well-formed while in C++17 it is ill-formed. The important thing to note is that a has static storage duration and that c++20 allows the address of certain subobjects(as opposed to C++17 which didn't allow "any" subobject address) to be template arguments.

    C++20

    The program is well-formed in c++20 as per temp.arg.nontype:

    For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):

    • a temporary object
    • a string literal object
    • the result of a typeid expression
    • a predefined func variable or
    • a subobject (6.7.2) of one of the above.

    (emphasis mine)

    Since &a[0] is neither of the above things mentioned in the list, the program is well-formed.

    Also note that the template argument must be a converted constant expression which is true here. From temp.arg:

    A template-argument for a non-type template-parameter shall be a converted constant expression ([expr.const]) of the type of the template-parameter. [ Note: If the template-argument is an overload set (or the address of such, including forming a pointer-to-member), the matching function is selected from the set ([over.over]). — end note ]

    Note a converted constant expression should also be a constant expression as per expr.const:

    A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only : * ...

    Now we move onto constant expression

    A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:

    • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a non-immediate function, or a null pointer value,

    C++17

    In C++17, the program is ill-formed as per temp.arg:

    A template-argument for a non-type template-parameter shall be a converted constant expression of the type of the template-parameter. For a non-type template-parameter of reference or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):

    • a subobject,

    (emphasis mine)