Document #: | P3579R1 [Latest] [Status] |
Date: | 2025-01-15 |
Project: | Programming Language C++ |
Audience: |
Core Working Group |
Reply-to: |
Matheus Izvekov <mizvekov@gmail.com> |
This paper is a follow-up to [P3310R5], and proposes to fix one additional issue which came with the incorporation of [P0522R0] into the standard.
The wording change failed to prevent a narrowing conversion for non-type template parameters when matching a template template-argument to a template-parameter.
This goes against the intent of P0522, which while it preserved the type-theoretically incorrect parameter pack exception, aimed to not add new such cases when matching template template parameters.
Consider the following example:
template<template<short> class> struct A {};
template<short> struct B;
template struct A<B>; // OK, exact match
template<int> struct C;
template struct A<C>; // #1: OK, all 'short' values are valid 'int' values
template<char> struct D;
template struct A<D>; // #2: error, not all 'short' values are valid 'char' values
The intention was that [P0522R0] would allow
#1
, but it
inadvertently also allowed
#2
: the
wording change did not implement the paper’s intent in this case, which
assumed that the ‘at least as specialized’ check would only imply that
“any template argument list that can legitimately be applied to the
template template-parameter is also applicable to the argument
template”.
The new rules delegated this matching to partial ordering of function templates, where a rewrite produced one each for the template parameter and the template argument.
But when matching these function templates against each other in the narrowing case, the template arguments produced from the non-type template parameters are value-dependent, and narrowing conversions are not diagnosed in this case, as in general dependent entities are not diagnosed if they have valid instantiations.
This issue also manifests itself in partial ordering:
template<template<short> class TT1> void f(TT1<0>); // #1
template<template<int> class TT2> void f(TT2<0>); // #2
template<int> struct B;
template void f<B>(B<0>); // selects #2
Before P0522, this selected
#2
.
After P0522, B matches both overloads, but they match each other during partial ordering, so this is now ambiguous.
The change proposed in this paper will make it so
#1
doesn’t
match #2
,
making #2
more specialized, restoring the original semantics.
Change 13.4.4 [temp.arg.template]/4
If the rewrite produces
an invalid typeis ill-formed, then P is not at
least as specialized as A.
[Example:
template<template<short> class P> struct S {};
template<int> struct A; template struct S<A>; // OK, not narrowing
Rewrite of the matching of the above template template parameter, in terms of function template partial ordering:
template<int> struct X {}; // Invented class X with template-head of A
template<short PP> void f(X<PP>); // #P
template<int PP> void f(X<PP>); // #A template void f<0>(X<0>); // Invoke partial ordering for exposition only. OK: selects #P
– end example]
[Example:
template<template<short> class P> struct S {};
template<char> struct B; template struct S<B>; // error: narrowing
Rewrite of the matching of the above template template parameter, in terms of function template partial ordering:
template<char> struct X {}; // Invented class X with template-head of B
template<short PP> void f(X<PP>); // #P
template<char PP> void f(X<PP>); // #A template void f<0>(X<0>); // Invoke partial ordering for exposition only. Bad: narrowing conversion on #P
As ordinary code, #P
is
valid, and partial ordering would select
#P
, but in the context of the
rewrite, the narrowing of PP to char is ill-formed.
– end example]
[Example:
template<template<short> class> void f(); // #1
template<template<int> class> void f(); // #2
template<int> struct B; template void f<B>(); // selects #2
– end example]