22

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?
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • 1
    A similar issue occurs for `int x = 0; constexpr int& r = x; template class X {}; X<&r>{};` As far as I can tell, the compilers reject these cases because they "don't follow" what an expression is referring to. I suspect they're directly using the mangled name appearing in the *id-expression* to create a name (for linkage purposes) for the (class) template specialization (or the Standard intends to allow such a simple implementation). – dyp Feb 22 '15 at 20:55
  • @dyp Check out [n4198](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4198.html). –  Feb 22 '15 at 21:02
  • @remyabel Thanks, I have already linked to [N4268](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4268.html) in my related question about pointers (see -->) – dyp Feb 22 '15 at 21:03
  • `g++` actually gives a sensible explanation to the issue. – didierc Feb 25 '15 at 20:12

1 Answers1

15

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.

Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • My copy of C++11 (the final draft, forgot the exact N-number) doesn't say "where id-expression is the name of an object or function". C++14 (N3936) does; I missed that. – Igor Tandetnik Feb 25 '15 at 20:38
  • @IgorTandetnik I will update my answer to reflect that it's not present in C++11, though the meaning is the same in previous standards; it still uses `id-expression`. – Filip Roséen - refp Feb 25 '15 at 20:41
  • I disagree that a reference doesn't denote the address of an object. See [DR 1570](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1570) (which added the wording in question). If your claim were true, no change would have been necessary and DR 1570 would be NAD. I'd say failure to prohibit `X` is a defect in C++11, corrected in C++14. – Igor Tandetnik Feb 25 '15 at 20:52
  • @IgorTandetnik a *"revised"* wording does not mean that the intent is not there, it merely means that it should be more precise - that's my take; do you have any proposal on how to make the wording of my answer any clearer (more accurate)? – Filip Roséen - refp Feb 25 '15 at 21:00
  • @IgorTandetnik See the updated (edited) answer, instead of trying to clearify things myself I took the liberty of adding a link to the relevant *DR*. What do you think? – Filip Roséen - refp Feb 25 '15 at 21:04
  • 1
    Thanks for the answer! 2 questions: 1) Why is the standard written the way that forbids using refernce as a non-type argument? I thought that, for all intents and purposes, a reference is the same as an object it references. Apparently here it's not, but why? 2) Is there a way to still use a reference and not an original object as a template argument? I wrote the template, so I can change its argument to, e.g. a pointer. – Osman-pasha Aug 31 '21 at 05:16