Non-type reference parameter/argument - c++

Why is it that the template argument of a non-type reference cannot be another reference (g++ 4.8.1):
template <int& N> void test() { }
int x = 5;
int& p = x;
int main(){
test<x>(); //compiles fine
test<p>(); //error: could not convert template argument 'p' to 'int&'|
}
I can't see where from the standard p is violating anything, these seemed the most relevant sections (N3337):
[14.3.2] [.1] 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 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, 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 .....
[.4]
[ Note: Temporaries, unnamed lvalues, and named lvalues with no linkage are not acceptable templatearguments
when the corresponding template-parameter has reference type.
[.5]
— For a non-type template-parameter of type reference to object, no conversions apply. The type referred
to by the reference may be more cv-qualified than the (otherwise identical) type of the templateargument.
The template-parameter is bound directly to the template-argument, which shall be an
lvalue.
p should be considered an lvalue shouldn't it? The only other thing I could think of was maybe a lack of linkage for references but adding extern int& p = x didn't fix it either.

This is related to the previous question template instantiation with constexpr function failure which I linked to a while ago in the comments, although your case is different.
It looks the example was previously not allowed but support was added in to C++1z via the proposal Allow constant evaluation for all non-type template arguments which opens with:
the syntactic restrictions for pointers, references, and pointers to
members are awkward and prevent reasonable refactorings. [...] The
historical reason for the restriction was most likely that C++
previously did not have a sufficiently strong specification for
constant expressions of pointer, reference, or pointer-to-member type.
However, that is no longer the case. [...]
The specific changes that seems relevant to your case is the rewording of the draft C++ standard section 14.3.2 Template non-type arguments [temp.arg.nontype]/p1 from:
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 durationtion
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
[...]
to:
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).
and a change of section 5.20 Constant expressions [expr.const]/p4 has the following paragraph on converted constant expressions, which starts out:
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
and this in particular was added:
[...] and where the reference binding (if any) binds directly [...]
Note, the current head version of clang compiles your code in C++1z mode, see it live.
The updated version of the N4268 was the one applied and clang C++1z implementation status section indicates this paper was support from clang 3.6. This code only works in C++1z mode for clang 3.6 and greater.

Using the simplified wording introduced by N4268 (and now in the WD),
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 […] type, the
value of the constant expression shall not refer to […]: […cases that don't apply…]
"converted constant expression" is defined in [expr.const]/4:
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 […] and where the reference binding (if any) binds directly.
Clearly, the reference binds directly. Are x and p constant expressions in this context?
[expr.const]/5:
A constant expression is either a glvalue core constant expression
whose value refers to an entity that is a permitted result of a
constant expression (as defined below), or […]
A permitted result of a constant expression is defined in the next paragraph as
…an object with static storage duration that is either not a temporary object or […]
x and p do refer to an object with static storage duration, but are they core constant expressions in the given context? The answer is yes: As long as their value (or the value of the object p refers to) is not examined by the expression, which it isn't, everything's fine, even for p:
A conditional-expression e is a core constant expression unless the evaluation of e […] would evaluate one of the following expressions:
— an id-expression that refers to a variable or data member of
reference type unless the reference has a preceding initialization and
either
it is initialized with a constant expression
x as the initializer of p is a constant expression (just as it is a valid template-argument for int&), hence p as the template-argument is a constant expression as well.
Note that Clang as of version 3.6 compiles your snippet fine.

Related

Does lvalue-to-rvalue conversion is applied to non-type lvalue template argument?

struct S{
constexpr S() {};
};
template <auto x> void f();
int main() {
S s{};
f<s>();
}
First off, per [temp.arg.nontype]/1
If the type T of a template-parameter (13.2) contains a placeholder
type (9.2.9.6) or a placeholder for a deduced class type (9.2.9.7),
the type of the parameter is the type deduced for the variable x in
the invented declaration
T x = template-argument;
If a deduced parameter type is not permitted for a template-parameter
declaration (13.2), the program is ill-formed.
Our template parameter contains a placeholder type auto, so the type of the parameter is the type deduced for the variable x in the invented declaration auto x = s; In this case, the type of the parameter is S, and S is a permitted type for the parameter declaration because S is a structural literal type.
Second, per [temp.arg.nontype]/2
A template-argument for a non-type template-parameter shall be a
converted constant expression (7.7) of the type of the
template-parameter.
This means the template-argument s shall be a converted constant expression. So per [expr.const]/10:
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
[..]
(10.2) — lvalue-to-rvalue conversions (7.3.2)
[..]
I'm not sure whether or not the lvalue s is converted to prvalue before any implicit conversions are applied to it. Note the definition of converted constant expression in C++14 is relatively changed. N4140 §5.19 [expr.const]/3: (emphasis mine)
A converted constant expression of type T is an expression, implicitly
converted to a prvalue of type T, where the converted expression is a
core constant expression and the implicit conversion sequence contains
only [..]
So per C++14, It's guaranteed that the converted expression is converted to a prvalue before any implicit conversion is applied to it.
I'm not so sure whether or not an lvalue-to-rvalue conversion is applied to template-argument s. In other words, I'm not sure that the lvalue s is converted to a prvalue.
So in general, if I passed an lvalue as a template argument for a non-type template parameter, does an lvalue-to-rvalue conversion applied to that lvalue?
And aside from that, Is the object s constant-initialized?
The meaning of [expr.const]/10 is that whenever an expression appears in a context that requires a "converted constant expression of type T", that expression is "implicitly converted to type T" and the additional restrictions in [expr.const]/10 shall apply.
So s is not "converted to a prvalue before any implicit conversions are applied to it". Rather, s is implicitly converted to type S, and this implicit conversion might involve some standard and/or user-defined conversions, such as an lvalue-to-rvalue conversion. To know whether or not it does, we would have to look at the rules for implicit conversions, which are in [conv.general]. In particular [conv.general]/6 states that
The effect of any implicit conversion is the same as performing the corresponding declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type ([dcl.ref]), an xvalue if T is an rvalue reference to object type, and a prvalue otherwise.
The expression E is used as a glvalue if and only if the initialization uses it as a glvalue.
I believe the wording here is a relic of older standard editions. What it should say (in modern language) is that when T is a non-reference type, an implicit conversion of E to T yields a prvalue that initializes its result object (call it t) as if by T t = E;.
So we consider S t = s; What does this initialization do? It calls the copy constructor of S to initialize t; [dcl.init.general]/16.6.2.1. There is no lvalue-to-rvalue conversion in this scenario. (Lvalue-to-rvalue conversions on class types are rare, but do occur in some places in the language, e.g., [expr.call]/12, [expr.cond]/7. If an lvalue-to-rvalue conversion were performed on s, it would also call the copy constructor; [conv.lval]/3.2. But in this particular case, the rules of the language do not require an lvalue-to-rvalue conversion.)
Consequently, the result of using s as a template argument for a template parameter of type S is that a prvalue is generated that initializes its result object by calling the copy constructor with s as the argument. (This particular copy constructor is implicitly defined to not do anything, since there are no subobjects to copy.)
This answers your question regarding whether an lvalue-to-rvalue conversion is applied. (You might be wondering what actually happens to the prvalue and whether the copy constructor actually gets called, but that's a separate topic that I don't want to get into right now, because it would make this answer too long.)
As for whether s is constant-initialized, yes it is. It satisfies (2.1) (since it has an initializer) and (2.2) since the full-expression of its initialization is a constant expression (there is nothing to stop it from being a constant expression, since it does nothing other than calling the constexpr default constructor, which itself does nothing). I'm not sure how this is relevant to your other question, though.

What is the difference between non-type template parameters in C++17 and C++11?

Consider this code:
using func = int (*)(int, int);
template<func F>
void do_something(int first, int second) {}
int something(int first, int second) { return 42; }
void f()
{
constexpr auto function = something;
do_something<function>(10, 20);
}
Which is compiled and run with C++17 standard compatible compiler, but it's fail with C++11 standard:
error: no matching function for call to ‘do_something<function>(int, int)’
17 | do_something<function>(10, 20);
What is the difference between C++11 non-type template parameters and C++17 non-type template parameters? in §14.1.4 [temp.param][n3690]:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
— integral or enumeration type,
— pointer to object or pointer to function,
— lvalue reference to object or lvalue reference to function,
— pointer to member,
— std::nullptr_t.
And in §17.1.4 [temp.param][n4713]:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
(4.1) — integral or enumeration type,
(4.2) — pointer to object or pointer to function,
(4.3) — lvalue reference to object or lvalue reference to function,
(4.4) — pointer to member,
(4.5) — std::nullptr_t, or
(4.6) — a type that contains a placeholder type (10.1.7.4).
The only difference is:
< — a type that contains a placeholder type (10.1.7.4).
Which I don't think is related to my question because a placeholder type is something like auto, and I sent a value to template not a placeholder type or a type.
The relevant difference is in the requirements on allowed template arguments (not template parameters) in [temp.arg.nontype].
C++11:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a constant expression that designates the address of an 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, 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
...
C++17:
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,
a temporary object,
a string literal,
the result of a typeid expression, or
a predefined __func__ variable.
In C++11, the template-argument function is not in the form & id-expression, and the name does not refer to the function something. It refers to a variable of type int (*const)(int, int), whose value points at something. (And do_something<&function> wouldn't help, because now you have a pointer to pointer to function, which won't convert to the pointer to function type.)
In C++17, the syntax requirement is gone, and the restriction is a more relaxed purely semantic requirement on what objects can't be pointed at or referenced.
C++11 [temp.arg.nontype]/1:
A template-argument for a non-type, non-template template-parameter shall be one of:
[...]
the name of a non-type template-parameter; or
a constant expression (5.19) that designates the address of an 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, 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
[...]
In other words, the form that a non-type template argument may take in C++11, in the case of a pointer, is heavily restricted. You can directly name the entity pointed to, as in &something, or, since this is a function, you may omit the & and allow the function-to-pointer conversion to take place, but you cannot use the name of an object that contains the pointer value, even if it is a constexpr object.
In C++17, almost all restrictions of this type were removed. In particular, a template argument for a non-type template parameter of function pointer type can be any converted constant expression of the appropriate function pointer type.

Why is this expression not a constant expression?

The expression b in this code shall be a core constant expression
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b; // Here
return 0;
}
since the standard says (8.20, paragraph 2 [expr.const] in n4700)
An expression e is a core constant expression unless the evaluation of
e would evaluate one of the following expressions:
...
an lvalue-to-rvalue conversion (7.1) unless it is applied to
...
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
First, the expression b in the above code is an lvalue (which is also a glvalue) since it's a reference, thereby being a variable (8.1.4.1, paragraph 1
[expr.prim.id.unqual]):
The expression is an lvalue if the entity is a function,
variable, or data member and a prvalue otherwise; it is a bit-field if the identifier designates a bit-field (11.5).
Second, the object the variable b denotes is a, and it's declared with constexpr. However, GCC complains:
./hello.cpp: In function ‘int main()’:
./hello.cpp:6:20: error: the value of ‘b’ is not usable in a constant expression
constexpr int c = b;
^
./hello.cpp:5:13: note: ‘b’ was not declared ‘constexpr’
const int &b = a;
As far as I can tell, a reference is not an object, so the above bullet apparently suggests that a shall be declared with constexpr. Am I missing something?
The reason why I don't agree with GCC is that GCC sees b as an object, thereby requiring it to be declared with constexpr. However, b is not an object!
One of the rules for core constant expressions is that we can't evaluate:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
b is an id-expression that refers to a variable of reference type with preceding initialization. However, it is initialized from a. Is a a constant expression? From [expr.const]/6:
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: [... ]
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
a is a glvalue core constant expression (it doesn't hit any of the restrictions in expr.const/2), however it is not an object with static storage duration. Nor is it a function.
Hence, a is not a constant expression. And b, as a result, isn't initialized from a constant expression and so can't be used in a core constant expression. And thus c's initialization is ill-formed as not being a constant expression. Declare a as a static constexpr int, and both GCC and Clang accept the program.
C++, you magical beast.
All quotes given in this answer are from the current working draft.
Given your example:
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b; // here
return 0;
}
First of all, let's take your example line by line. The first line is a definition of a constexpr variable named by identifier a:
constexpr int a = 10;
The initializer 10 is an integral constant expression, hence it's a core constant expression, per [expr.const]/9:
An integral constant expression is an expression of integral or
unscoped enumeration type, implicitly converted to a prvalue, where
the converted expression is a core constant expression. [ Note: Such
expressions may be used as bit-field lengths, as enumerator
initializers if the underlying type is not fixed, and as alignments.
 — end note ]
And it's a constant expression because it's a prvalue core constant expression that satisfies the [expr.const]/12 constraints:
A constant expression is [..] a prvalue core constant expression
whose value satisfies the following constraints:
(12.1) if the value is an object of class type, each non-static data
member of reference type refers to an entity that is a permitted
result of a constant expression,
(12.2) 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
(12.3)
if the value is of pointer-to-member-function type, it does not
designate an immediate function, and
(12.4) if the value is an object
of class or array type, each subobject satisfies these constraints for
the value.
Hence, the initializer expression 10 is a prvalue core constant expression that's neither a pointer type nor a class type nor an array type. Hence, it's a constant expression.
The second line declares/defines a reference to a named by identifier b:
const int &b = a;
In order to know whether the initializer expression a is a core constant expression, you have to invoke [expr.const]/5:
An expression E is a core constant expression unless the evaluation
of E, following the rules of the abstract machine ([intro.execution]),
would evaluate one of the following: [..]
At this point, the evaluation of E does not evaluate any of the constraints defined in [expr.const]/5. Therefore, the expression E is a core constant expression.
But being E is a glvalue core constant expression that doesn't mean that E is also constant expression; so you have to check [expr.const]/12:
A constant expression is either a glvalue core constant expression
that refers to an entity that is a permitted result of a constant
expression [..]
An entity is a permitted result of a constant expression if it is an
object with static storage duration that either is not a temporary
object or is a temporary object whose value satisfies the above
constraints, or if it is a non-immediate function.
So the entity a is not a permitted result of constant expression because that entity neither refers to an object with static storage duration nor a temporary object or non-immediate function. Therefore, the glvalue core constant expression a is not a constant expression.
The third line defines an constexpr variable named by identifier c initialized by the variable b:
constexpr int c = b;
Again you have to invoke [expr.const]/5 in order to know whether the expression b is a core constant expression or not. As you already noticed, the bullet (5.8) maybe satisfied since the initialization of c involves an lvalue-to-rvalue conversion. So per [expr.const]/5
An expression E is a core constant expression unless the evaluation of
E [..] would evaluate one of the following:
(5.8) an lvalue-to-rvalue conversion unless it is applied to
(5.8.1) a non-volatile glvalue that refers to an object that is usable in constant expressions, or
(5.8.2) a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
Note that the document to which I refer doesn't have the following bullet. But C++20 documents have it. And I don't know why it's removed from the current working draft. ([expr.const]/(5.12)):
(5.12) an id-expression that refers to a variable or data member of
reference type unless the reference has a preceding initialization and
either
(5.12.1) it is usable in constant expressions or
(5.12.2) its
lifetime began within the evaluation of E;
Even if it's checked, the expression E is still not a core constant expression because the reference is not usable in constant expressions (see below) and its lifetime doesn't begin within the evaluation of E.
Back to our explanation. The bullet (5.8) is satisfied since the expression E evaluates an lvalue-to-rvalue conversion. So (5.8.1) is tried first.
Indeed, the lvalue-to-rvalue conversion is applied to a non-volatile glvalue (b). But, Is this glvalue refers to an object that's usable in constant expressions? First, you have to check whether it's a constant-initialized variable, then also you have to check whether it's a potentially-constant variable, so [expr.const]/2 is checked first:
A variable or temporary object o is constant-initialized if
(2.1) either it has an initializer or its default-initialization results in some initialization being performed, and
(2.2) the full-expression of its initialization is a constant expression when interpreted as a constant-expression [..]
The first bullet (2.1) is satisfied because the variable b has an initializer. But the second bullet isn't satisfied because the full-expression of its initialization is not a constant expression because its initializer a is not a constant expression as aforementioned. Therefore, the variable b is not constant-initialized.
Just for completeness, the [expr.const]/3 is tried:
A variable is potentially-constant if it is constexpr or it has a
reference or const-qualified integral or enumeration type.
Since the variable b has a reference type, it's considered to be potentially-constant. Note, there's no need to check whether the variable b is potentially-constant because intuitively you can't even enter the [expr.const]/4 since it requires the variable to be constant-initialized:
A constant-initialized potentially-constant variable is usable in
constant expressions at a point P if its initializing declaration D is
reachable from P [..]
(emphasis mine)
Even though the variable is potentially-constant, it's not usable in a constant expression because it's not constant-initialized. So we ended up with that the variable b is not usable in constant expressions (as the GCC diagnostic hints to you). Therefore, the bullet (5.8.1) is not satisfied: Hence, the expression E is not a core constant expression and therefore not a constant expression.

Using constexpr array as a template non-type argument (C++14)

#include <iostream>
using namespace std;
template<const int arr[]>
struct S {
static constexpr int value = arr[0];
};
constexpr int arr[] = { 5 };
int main() {
cout << S<arr>::value << endl;
}
This program compiles fine and prints 5 with gcc 5.1 and higher, but MSVC 19.10.25019 gives the following errors:
error C2975: 'S': invalid template argument for 'arr', expected
compile-time constant expression error C2131: expression did not
evaluate to a constant
Is this program valid according to the C++14 Standard, or gcc is being too lenient here?
The program is well-formed as far as I can see.
According to [temp.param]/8 the template parameter actually has type const int*, not const int[].
A non-type template-parameter of type “array of T” or “function returning T” is adjusted to be of type
“pointer to T” or “pointer to function returning T”, respectively.
According to [temp.arg.nontype]/1, we can use the name of a complete array object with static storage duration and external linkage as an argument to such a template parameter:
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 ...
arr is a constant expression, despite the fact that MSVC thinks it isn't. It is a core constant expression according to [expr.const]/2 since it does not contain any forbidden evaluations, and it is a constant expression since it points to an object with static storage duration ([expr.const]/4).
Because the template parameter refers to an array with static storage duration, the bounds of the array are known at the time of template instantiation. It can therefore verify that the access to arr[0] is a legitimate core constant expression since it has well-defined behaviour and falls into the category of allowed lvalue-to-rvalue conversions in [expr.const]/2:
... a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with
a preceding initialization, initialized with a constant expression

Reference as a non-type template argument

The example below attempts to use a variable of reference type as an argument for a non-type template parameter (itself of reference type). Clang, GCC and VC++ all reject it. But why? I can't seem to find anything in the standard that makes it illegal.
int obj = 42;
int& ref = obj;
template <int& param> class X {};
int main()
{
X<obj> x1; // OK
X<ref> x2; // error
}
Live example
CLang says:
source_file.cpp:9:7: error: non-type template argument of reference type 'int &' is not an object
Others complain in similar ways.
From the standard (all quotes from C++11; C++14 doesn't appear to have significant changes in relevant parts):
14.3.2/1 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 an object with static storage duration and external or internal linkage ... expressed (ignoring parentheses) as & id-expression, except that the & ... shall be omitted if the corresponding template-parameter is a reference
...
Now what's a constant expression:
5.19/2 A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2)...
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization, initialized with a constant expression
...
As far as I can tell, ref in X<ref> is an id-expression that refers to a variable of reference type. This variable has a preceding initialization, initialized with the expression obj. I believe obj is a constant expression, and anyway if it isn't, then X<obj> shouldn't compile either.
So what am I missing?
Which clause in the standard renders X<ref> invalid, while X<obj> is valid?
Introduction
It is correct saying that the name of a reference is an id-expression, though; the id-expression doesn't refer to whatever the reference is referencing, but the reference itself.
int a = 0;
int& ref = a; // "ref" is an id-expression, referring to `ref` - not `a`
The Standard (N4140)
You are quoting the relevant sections of the standard in your post, but you left out the most important part (emphasize mine):
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:
...
a constant expression (5.19) that designates the address of a complete object with static sturage 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 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; ...
Note: In earlier drafts "where id-expression is the name of an object or function" isn't present; it was addressed by DR 1570 - which undoubtedly makes the intent more clear.
A variable of reference type is not an object?
You are absolutely correct; the reference itself has reference type, and can merely act as an object when part of an expression.
5p5 Expressions [expr]
If an expression initially has the type "reference to T" (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Elaboration
It is very important to note that the constant expression ("that designates the address of a complete object...") must be one of &id-expression, or id-expression.
Even though a constant-expression, that isn't just an id-expression, might refer to an object with static storage duration, we cannot use it to "initialize" a template-parameter of reference- or pointer type.
Example Snippet
template<int&>
struct A { };
int a = 0;
constexpr int& b = (0, a); // ok, constant-expression
A<(0, a)> c = {}; // ill-formed, `(0, a)` is not an id-expression
Note: This is also a reason behind the fact that we cannot use string-literals as template-arguments; they are not id-expressions.