Non-type reference parameter/argument

2019-02-24 08:04发布

问题:

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.

回答1:

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.



回答2:

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.