CWG Issue 2307 (original) (raw)
This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 117a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2025-04-13
2307. Unclear definition of “equivalent to a nontype template parameter”
Section: 13.8.3.2 [temp.dep.type]Status: CD5Submitter: Richard SmithDate: 2016-07-21
[Accepted as a DR at the November, 2017 meeting.]
The description of whether a template argument is equivalent to a template parameter in 13.8.3.2 [temp.dep.type] paragraph 3 is unclear as it applies to non-type template parameters:
A template argument that is equivalent to a template parameter (i.e., has the same constant value or the same type as the template parameter) can be used in place of that template parameter in a reference to the current instantiation. In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression in which the template parameter appears as a subexpression.
For example:
template struct A { typedef int T[N]; static const int AlsoN = N; A::T s; // #0, clearly supposed to be OK static const char K = N; A::T t; // #1, OK? static const long L = N; A::T u; // #2, OK? A<(N)>::T v; // #3, OK? static const int M = (N); A::T w; // #4, OK? };
#1 narrows the template argument. This obviously should not be the injected-class-name, because A<257>::T may well be int[1] not int[257] . However, the wording above seems to treat it as the injected-class-name.
#2 is questionable: there is potentially a narrowing conversion here, but it doesn't actually narrow any values for the original template parameter.
#3 is hard to decipher. On the one hand, this is an expression involving the template parameter. On the other hand, a parenthesized expression is specified as being equivalent to its contained expression.
#4 should presumably go the same way that #3 does.
Proposed resolution (August, 2017):
Change 13.8.3.2 [temp.dep.type] paragraph 3 as follows:
A template argument that is equivalent to a template parameter
(i.e., has the same constant value or the same type as the template parameter)can be used in place of that template parameter in a reference to the current instantiation. For a template_type-parameter_, a template argument is equivalent to a template parameter if it denotes the same type. For a non-type template parameter, a template argument is equivalent to a template parameter if it is an_identifier_ that names a variable that is equivalent to the template parameter. A variable is equivalent to a template parameter if
- it has the same type as the template parameter (ignoring cv-qualification) and
- its initializer consists of a single identifier that names the template parameter or, recursively, such a variable.
[Note: Using a parenthesized variable name breaks the equivalence. —_end note_]
In the case of a non-type template argument, the argument must have been given the value of the template parameter and not an expression in which the template parameter appears as a subexpression.[Example:template class A { A* p1; // A is the current instantiation A* p2; // A is the current instantiation A<T*> p3; // A<T*> is not the current instantiation ::A* p4; // ::A is the current instantiation class B { B* p1; // B is the current instantiation A::B* p2; // A::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the current instantiation }; };
template class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A* p2; // A is not the current instantiation };
template <class T1, class T2, int I> struct B { B<T1, T2, I>* b1; // refers to the current instantiation B<T2, T1, I>* b2; // not the current instantiation typedef T1 my_T1; static const int my_I = I; static const int my_I2 = I+0; static const int my_I3 = my_I; static const long my_I4 = I; static const int my_I5 = (I); B<my_T1, T2, my_I>* b3; // refers to the current instantiation B<my_T1, T2, my_I2>* b4; // not the current instantiation B<my_T1, T2, my_I3>* b5; // refers to the current instantiation B<my_T1, T2, my_I4>* b6; // not the current instantiation B<my_T1, T2, my_I5>* b7; // not the current instantiation };
—_end example_]
Additional note (November, 2017):
It was observed that the proposed resolution does not address partial specializations, which also depend on the definition of equivalence. For example:
template <typename T, unsigned N> struct A; template struct A<T, 42u> { typedef int Ty; static const unsigned num = 42u; static_assert(!A<T, num>::Ty(), ""); }; A<int, 42u> a; // GCC, MSVC, ICC accepts; Clang rejects
The issue is being returned to "review" status in order to consider these additional questions.
Notes from the November, 2017 (Albuquerque) meeting:
CWG decided to proceed with this resolution and deal with partial specialization in a separate issue.