Reputation:
Consider this program:
template <int F(), int N = F()>
void f() { }
constexpr int g() { return 1; }
int main() { f<g>(); }
Is this valid? Are compilers required to see at template definition time, that F
could potentially refer to a constexpr
function, and therefore the default argument for N
could be valid?
gcc and clang accept this, but Intel1 rejects the template function at template definition time because F()
is not a constant expression. Intel does accept f<g, g()>()
if the default argument is removed, so clearly it understands that g()
is usable in a constant expression in general.
It's not clear to me what the standard says. It's clear that (C++11 [expr.const]p2)
an invocation of a function other than a
constexpr
constructor for a literal class or aconstexpr
function
renders an expression non-constant, but it's not clear to me whether that applies here. At template definition time, it certainly does seem to apply, since F
is not declared to be a constexpr
function, but at the same time, errors at template definition time are supposed to be diagnosed only if there is no possible valid instantiation of the template, and there does appear to be a valid instantiation here.
I can see the arguments for both answers, so I'm getting confused. Is there a definitive answer to this question?
1. Re-testing with the current release of Intel's compiler shows that it works just fine, so presumably the Intel developers considered it a bug and have since fixed it. This is a huge hint that the code is meant to be valid. It would still be nice to get a conclusive answer based on the standard, though.
Upvotes: 31
Views: 1822
Reputation: 63902
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.
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 type
T
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:
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
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
Upvotes: 8