Martin Martin - 11 months ago 76
C++ Question

can a static constexpr variable be used as a template argument

I have the following piece of code which compiles on older gcc, but not on version 6 (works with -std=c++1z). Clang rejects it too, saying the object val doesn't have the right kind of linkage. I don't understand the difference. Isn't a constexpr variable of a pointer type supposed to work more or less transparently? Is there something I'm missing in the syntax that would allow this to work? Or is this violating some portion of the standard?

typedef void(*t_voidfn)();
template <t_voidfn> struct s {};
void fn() {
static constexpr t_voidfn val = &fn;
s<val> x;

On the other hand this one works.

typedef void(*t_voidfn)();
template <t_voidfn> struct s {};
void fn() {
s<&fn> x;

Answer Source

The first snippet is correct in C++17, but not in C++14 and 11.

For C++14, [temp.arg.nontype]/1 says:

A template-argument for a non-type, non-template template-parameter shall be one of:


  • a constant expression (5.19) that designates the address of a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, where the id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
  • a constant expression that evaluates to a null pointer value (4.10); or
  • a constant expression that evaluates to a null member pointer value (4.11); or
  • a pointer to member expressed as described in 5.3.1; or
  • a constant expression of type std::nullptr_t.

(I've included only the bullets that are directly relevant to pointers and pointers to members.)

Basically, the address of the function in your sample has to be expressed strictly as &fn or fn.

C++11 contains essentially the same wording minus a couple of clarifications introduced by defect reports between 11 and 14:

  • DR1570 clarified the bit about complete objects;
  • DR1398 amended by DR1666 added the last bullet.

For C++17, the restrictions have been relaxed as a result of the adoption of paper N4268 (rationale in N4198). The corresponding paragraph (2) now says:

A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) 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 (1.8),
  • a temporary object (12.2),
  • a string literal (2.13.5),
  • the result of a typeid expression (5.2.8), or
  • a predefined __func__ variable (8.4.1).

[ 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). — end note ]

N4198 contains good explanations for each of those bullets.