Introduction
template<int F(), int N = F()> void func ();
In this answer we will go through the relevant sections of the International Standard, step by step, to prove that the above snippet is well-formed.
What does the International Standard (N3337) say?
The Standardese
14.1p9 Template parameters
[temp.param]
A default template-argument is a template-argument (14.3) specified after
=
in a template-parameter. […]
14.3p6 Template arguments
[temp.arg]
If the use of a template-argument gives rise to an ill-formed construct in the instantiation of a template specialization, the program is ill-formed.
14.3.2p1 Template non-type arguments
[temp.arg.nontype]
A template-argument for a non-type, non-template template-parameter shall be one of:
- for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
- the name of a non-type template-parameter; or
- a constant expression (5.19) that designates the address of an object […]; 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
5.19p3 Constant expressions
[expr.const]
A literal constant expression is a prvalue core constant expression of
literal type, but not pointer type. An integral constant expression is a
literal constant expression of integral or unscoped enumeration type. A converted constant expression of typeT
is a literal constant expression,
implicitly converted to the typeT
, […]
8.3.6p3 Default arguments
[dcl.fct.default]
A default argument shall be specified only in the parameter-declaration-clause of a function declaration or in a template-parameter (14.1); in the latter case, the initializer-clause shall be an assignment-expression.
The Verdict
The above sections makes us come to the following conclusions:
- A default template-argument is a template-argument, and;
- when instantiating a template, all template-arguments must be usable in the context where they appear, and;
- every template-argument for a non-type, non-template template-parameter that appears in a program must be a literal constant expression, and;
- the default-argument for a template-parameter shall be an assignment-expression.
The Explanation
template<int F(), int N = F()>
void func ();
constexpr int (*F)() = <some_initializer>; // (A)
constexpr int N = <explicit_template_argument> OR <F()> // (B)
The snippet above can be used as a mental helper to ease reasoning about what the template-parameters will be equivalent to, given a set of template-arguments.
To see whether (B) is valid or not, where an explicit template-argument is not given for N, we must evaluate (A) – and the evaluation of (A) might yield a value for F that is usable in the constant-expression required by (B).
With that said; Yes, the template is legal C++11.
Legal
constexpr int g () { ... }
// func<&g>
constexpr int (*F)() = &g; // ok
constexpr int N = F(); // ok
Ill-formed
int f () { ... }
// func<&f>
constexpr int (*F)() = &f; // ok
constexpr int N = F(); // ill-formed, not a constant-expression
Bonus
The same set of rules apply to the following template;
template<int X, int N = 100/X>
void gunc ();
gunc<0> (); // ill-formed, `100/0` is not mathematically defined,
// and is therefore not a constant-expression
For the language-lawyer
And this, pointless use of a default template-argument, is actually legal since F()
might be a constant-expression.
F()
can however not be a converted constant-expression to give N a value, but this doesn’t happen until (if ever) the default argument is actually used.
template<void F(), int N = F()>
void hunc ();
void f ();
hunc<&f, 10> (); // legal
hunc<&f > (); // ill-formed