<html>
<head>
<title>P1907R0: Inconsistencies with non-type template parameters</title>

<style type="text/css">
  ins { text-decoration:none; font-weight:bold; background-color:#A0FFA0 }
  .new { text-decoration:none; font-weight:bold; background-color:#D0FFD0 }
  del { text-decoration:line-through; background-color:#FFA0A0 }  
  strong { font-weight: inherit; color: #2020ff }
  table, td, th { border: 1px solid black; border-collapse:collapse; padding: 5px }
</style>
</head>

<body>
ISO/IEC JTC1 SC22 WG21 P1907R0<br/>
Jens Maurer &lt;Jens.Maurer@gmx.net><br/>
Target audience: EWG<br/>
2019-10-07<br/>

<h1>P1907R0: Inconsistencies with non-type template parameters</h1>

<h2>Introduction</h2>

Non-type template parameters were originally limited to having scalar
non-floating-point types.

Through a number of recent changes, non-type template parameters of
class type are now supported as well, provided they have strong
structural equality, in particular no user-defined operator== for any
of the class's subobjects.

<ul>
<li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0732r2.pdf">P0732R2</a> Class Types in Non-Type Template Parameters</li>

<li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1185r2.html">P1185R2</a> &lt;=> != ==</li>

<li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1630r1.html">P1630R1</a> Spaceship needs a tune-up, Addressing some discovered issues with P0515 and P1185</li>
</ul>

<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1714r1.html">P1714R1</a> (NTTP are incomplete without float, double, and long double!),
which added floating-point types as permissible non-type template parameters
(with bit-wise comparison on the value representation),
was rejected by plenary straw poll in Cologne.
<p>
We should strive to remove or avoid the following categories of inconsistencies:
<ul>
<li>(A) The run-time result of <code>x == y</code> differs
  from the compile-time result of <code>std::is_same_v&lt;A&lt;x>, A&lt;y>></code> for a suitably chosen template <code>A</code>.</li>
<li>(B) The behavior of <code>x</code> as a top-level template
  argument differs from its behavior as a member of a class, a value
  of which is used as such a template argument.</li>
</ul>

This paper explores those inconsistencies, caused by the symbolic
representation of template arguments vs. their bit-pattern runtime
value, and suggests remediations.

<h2>Changes</h2>

(initial revision)

<h2>Review of inconsistencies</h2>

Here are the currently known inconsistencies, with a classification
into the two categories above.

<h3>Pointer-to-member types</h3>

<pre>
  union U {
    int x;
    int y;
  };
  template&lt;int U::*p> struct A;
</pre>

<code>&amp;U::x</code> and <code>&amp;U::y</code> (obviously) denote
different members of <code>U</code>. According to
[temp.type] p1.3,

<blockquote>
Two <em>template-id</em>s refer to the same class [...] if [...] their
corresponding non-type <em>template-argument</em>s of
pointer-to-member type refer to the same class member [...]
</blockquote>

<code>A&lt;&U::x></code> and <code>A&lt;&U::y></code> are
therefore different types.
<p>
However, [expr.eq] p4.5 says about run-time comparison with <code>==</code>:
<blockquote>
  If both refer to (possibly different) members of the same union (11.4), they compare equal.
</blockquote>
Thus, <code>&U::x == &U::y</code> is <code>true</code>.
<p>
This is a case of both a type (A) and a type (B) inconsistency,
because a pointer-to-member used as a class data member in a template
argument will be compared with <code>==</code>, but as a top-level
template argument, it will use the special "same class member" rule
quoted above.

<h3>Floating-point types</h3>

Floating-point types cannot be used as non-type template parameters
and cannot be members of classes usable as types for non-type template
parameters, because built-in <code>&lt;=></code> comparison yields
<code>std::partial_ordering</code> (see [expr.spaceship] p4.3).
<p>

With <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1714r1.html">P1714R1</a>
(NTTP are incomplete without float, double, and long double!)
applied, values of floating-point type would have been permissible as
a top-level template argument, but not as a class member. Thus, a type
(B) inconsistency would exist because the use as a class member is
ill-formed. Further, a type (A) inconsistency would be introduced.
For example:

<pre>
  template&lt;float x> struct A;
  static_assert&lt;!std::is_same_v&lt;A&lt;+0.0>, A&lt;-0.0>>  // holds per P1714R1; different value representations
  constexpr bool b = +0.0 == -0.0;                 // true according to IEEE floating-point
</pre>

<h3>References</h3>

References can appear as top-level non-type template parameters, with
the following "same type" semantics:
<blockquote>
Two <em>template-id</em>s refer to the same class [...] if [...]
their corresponding non-type template-arguments of reference type
refer to the same object or function [...]
</blockquote>

According to [class.compare.default] p2, the
defaulted <code>operator==</code> is defined as delete if a class
contains a reference data member:
<blockquote>
A defaulted comparison operator function for class C is defined as
deleted if any non-static data member of C is of reference type or C
is a union-like class (11.4.1).
</blockquote>

Thus, such a class is unusable as a non-type template parameter for
lack of strong structural equality, introducing a type (B)
inconsistency.
<p>
A type (A) inconsistency is present, because <code>x ==
y</code> obviously does not consider whether <code>x</code>
and <code>y</code> refer to the same object, but simply applies the
lvalue-to-rvalue conversion, erasing any notion of object identity.

<h3>Types with user-defined <code>operator==</code></h3>

A type with a user-defined <code>operator==</code> may exhibit a type
(A) inconsistency, depending on the definition
of <code>operator==</code>. Both class types and enum types may have a
user-defined <code>operator==</code>.  In the status quo, an absent or
non-defaulted <code>operator==</code> for a class type prevents the
use of that class as the type of a non-type template parameter.  There
is currently no comparable rule for enums, which leads to some
underspecification
(see <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1837r0.html">P1837R0</a>
Remove NTTPs of class type from C++20 by Arthur O'Dwyer).


<h2>Suggestions</h2>

<h3>(1) Divorce template argument equality from <code>operator==</code></h3>

If we would define template argument equality for classes as a special
kind of equality, unrelated to <code>operator==</code>, we could apply
the special equality rules in [temp.type] p1 also to class members of
the appropriate type.  Ancillary restrictions, such as restricting the
permissible pointer values for non-type template arguments of pointer
type, already apply uniformly to class members as well as top-level
arguments; see [temp.arg.nontype] p2.
<p>
Whether a class is suitable as the type of a non-type template
parameter in the first place, however, still needs to be determined.
The following are possible options:
<ul>
  <li>Presence of a user-defined <code>operator==</code>: After all,
if a user defines a non-defaulted <code>operator==</code> somewhere,
it is a clear sign the class has special equality semantics, likely
causing even more surprising type (A) inconsistencies.  For the
specific case of enums, which cannot have a
defaulted <code>operator==</code> in their definition (because the
    syntax doesn't allow it), a declaration of <code>operator==</code>
    after the first use of that enum in a non-type template parameter
    is considered hostile.
  </li>
  <li>No restriction: Since <code>operator==</code> is not involved
    in template argument equality anymore, there is no reason to refer
    to it in any way. Unfathomable instantiations are prevented by the
    rules on permissible template arguments; see [temp.arg.nontype] p2.</li>
  <li>Trivial copyability: If a type is trivially copyable,
    its value is preserved by <code>memcpy</code>.  However, trivial
    copyability might not be related to comparison semantics.</li>
</ul>
<p>
Regardless of the restriction chosen, the approach avoids any type (B)
inconsistencies, but introduces more type (A) inconsistencies.
However, type (A) inconsistencies appear to be historically
acceptable, given the long-standing existing rules on
pointer-to-members and references.

<h3>(2) Prohibit "bad" types as class members</h3>

We already prohibit references as members of classes for non-type
template parameters, and we could do the same for pointer-to-member
types.  This would introduce another type (B) inconsistency, but
(similar to the reference and floating-point cases) one where one of
the outcomes is an ill-formed program.

<h2>Recommendation</h2>

Let's go with (1).

</body>
</html>

