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.
&fnorfn. This is simply a restriction specified in the Standard for C++14 and below, which was lifted for C++17. This is why the code compiles on both compilers in C++1z mode. cc @KerrekSB