-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathp1714.html
204 lines (148 loc) · 8.88 KB
/
p1714.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<html>
<head>
<title>NTTP are incomplete without float, double, and long double! </title>
<meta Author="Jorg Brown" content="proposal">
</head>
<body bgcolor="#FFFFFF" text="#000000">
<style>
del { background-color:LightPink; }
ins { background-color:LightGreen; }
</style>
<font size=-1>
Jorg Brown <jorg.brown@gmail.com>
<br>
2019-07-19
<br>
Document P1714R1
<code>$Id: proposal.html,v 1.50 2019/07/19 02:12:37 jorg Exp $</code>
</font>
<h1>P1714: NTTP are incomplete without float, double, and long double!
(Revision 1)</h1>
<h2>Revision history</h2>
<ul>
<li>2019-06-18: R0: Proposal to allow float, double, and long double template parameters.</li>
<li>2019-07-19: R1: Proposal to allow float, double, and long double template parameters.
<ul>
<li>Rather than modify paragraph 7 of 13.1 [temp.param], it is deleted entirely.</li>
<li>Support for floating point is specifically specified in paragraph 4 instead.</li>
<li>Section 13.5 Type equivalence [temp.type] is updated for floating-point since operator== will not work.</li>
<li>__cpp_nontype_template_paramater_class is suggested as the feature test macro to use.</li>
<li>Minor commentary updates and typo fixes.</li>
</ul>
</li>
</ul>
<h2>Introduction.</h2>
<p>
For decades, template parameters could be either types or constants of certain trivial types: integers, enums, pointers, etc. Notably absent from this list were floating-point values. Recently, the adoption of P0732 has allowed constants of class type to be used as template parameters. Furthermore, P0476 allows us to perform constexpr bit-casting of floating-point values. And in the decades since floating-point types were banned from use as template parameters, compile-time computation of floating-point values has advanced dramatically.
</p>
<p>
This paper, P1714. proposes to include floating-point values into the list of acceptable template parameters.
</p>
<h2>I. Motivation and Scope.</h2>
<p>
Consider the pow() function. The most general implementation uses log and exp:
<blockquote>
<pre>
double pow(double base, double exponent) {
return exp(log(base) * exponent);
}
</pre>
</blockquote>
This has several disadvantages, one being that if exponent is an integer, the exactness available through multiplication isn't achieved due to round-off error in exp and log. So we end up in the unfortunate situation that raising an integer to an integral power sometimes produces a result that is very close to, but not equal to, an integer. Similarly, often a number is raised to the power 1/2 in an attempt to obtain a square root, esp. by programmers from other languages who are unaware that the standard library offers an extremely accurate square-root instruction.
</p>
<p>
But suppose we could specify the exponent:
<blockquote>
<pre>
template<double exponent>
double pow(double base);
</pre>
</blockquote>
The default implementation of such a function could use the log/exp solution, while the code could be specialized for common integer powers and binary fractions (1/2, 1/4) to produce far more accurate results - and to produce them faster. There's just one problem: the floating-point exponent is not allowed as a template parameter.
</p>
<p>
With the new facilities of C++20, we can work around this problem: (Working demo at <a href="https://godbolt.org/z/FaeQc4">Compiler Explorer</a>)
<blockquote>
<pre>
template<typename T> struct AsTemplateArg {
std::array<char, sizeof(T)> buffer = {};
constexpr AsTemplateArg(const std::array<char, sizeof(T)> buf) : buffer(buf) {}
constexpr AsTemplateArg(T t) : AsTemplateArg(std::bit_cast<std::array<char, sizeof(T)> >(t)) {}
constexpr operator T() const { return std::bit_cast<T>(this->buffer); }
};
template<AsTemplateArg<double> exponent>
double pow(double base) {
return exp(log(base) * double{exponent});
}
template<>
double pow<AsTemplateArg<double>{1.0}>(double base) {
return base;
}
</pre>
</blockquote>
<p>
But why? Let's just let the compiler do what it can do very easily, rather than force the use of a bunch of bit-cast boilerplate.
</p>
<h2>Impact on the Standard</h2>
<p>
Portions of the standard which currently prohibit use of floating-point constants as template parameters shall be removed.
</p>
<h2>Proposed Wording</h2>
<p>Note: All changes are relative to the 2019-06-13 working draft of C++20.</p>
<p>Modify 13.1 Template parameters [temp.param] as follows:</p>
<p>Modify paragraph 4 to add floating-point:</p>
<blockquote>
<p>A non-type template-parameter shall have one of the following (optionally cv-qualified) types:</p>
<p>-- a literal type that has strong structural equality ([class.compare.default]),</p>
<p><ins>-- a floating-point type.</ins></p>
<p>-- an lvalue reference type,</p>
<p>-- a type that contains a placeholder type ([dcl.spec.auto]), or</p>
<p>-- a placeholder for a deduced class type ([dcl.type.class.deduct]).</p>
</blockquote>
<p>Delete paragraph 7 (which begins with <del>"A non-type template-parameter shall not be declared to..."</del>]</p>
<p>Modify 13.5 Type equivalence [temp.type] paragraph 1 as follows:</p>
<blockquote>
<p>Two <i>template-ids</i> refer to the same class, function, or variable if<ins>, after converting each <i>template-argument</i> to match the kind and type of its corresponding <i>template-parameter,</i> if needed,</ins></p>
<p>-- their <i>template-names,</i> <i>operator-function-ids,</i> or <i>literal-operator-ids</i> refer to the same template and</p>
<p>-- their corresponding type <del><i>template-arguments</i></del><ins>template arguments</ins> are the same type and</p>
<p>-- their corresponding non-type <del><i>template-arguments</i></del><ins>template arguments</ins> of pointer-to-member type refer to the same class member or are both the null member pointer value and</p>
<p>-- their corresponding non-type <del><i>template-arguments</i></del><ins>template arguments</ins> of reference type refer to the same object or function and</p>
<p><ins>-- their corresponding non-type template arguments of floating-point type have identical value representations and</ins></p>
<p>-- their remaining corresponding non-type <del><i>template-arguments</i></del><ins>template arguments</ins> have the same type and <del>value after conversion to the type of the <i>template-parameter,</i> where they are considered to have the same value if they</del> compare equal with the == operator ([expr.eq]), and</p>
<p>-- their corresponding template <i>template-arguments</i> refer to the same template.</p>
</blockquote>
<p>I propose to update the existing feature test macro, __cpp_nontype_template_parameter_class, for this feature.</p>
<h2>Alternatives Considered</h2>
<p>
Originally the suggested design was to decompose a floating-point type into sign, exponent, and mantissa, and then use the existing P0732 wording to allow that triplet of values to represent the floating-point constant in question. This was changed because:
</p>
<p>
1) It's more work for the compiler. All compilers must already know how to represent floating-point values in bit form for their target architecture, in case the user declares a global floating-point value with an initial value. And that bytewise representation satisfies P0732's requirements for template parameter, no decomposition needed.
</p>
<p>
2) Such a decomposition does not distinguish between positive zero and negative zero, which would prohibit the implementation of a function such as pow, which distinguishes between positive and negative zero. Even printf is defined to treat +0.0 and -0.0 differently.
</p>
<p>
3) Such a decomposition proves difficult for INF and NaN values, especially since <a href="http://wg21.link/P0533">P0533</a> has not yet been adopted.
</p>
<h2>Downsides</h2>
<p>
Using bit-level equality rather than the type's underlying operator== means that if you specialize a template that uses float/double parameters, using 0.0 as your specialization, then your specialization will not impact code that passes -0.0 as a parameter. (But note that the difference between 0.0 and -0.0 is observable at runtime. For example, 1/0.0 produces +INFINITY, while 1/-0.0 produces -INFINITY)
</p>
<p>
There is a related impact with NaNs; attempting to specialize such a template using a value of NaN or -NaN will only specialize those two NaNs, rather than the full range of NaNs that exist. Nevertheless, this is not expected to be an issue; expressions which produce infinities and nans are not allowed at compile-time. Also, if a user wants to have different template behavior for NaNs, it's a simple matter of adding:
</p>
<blockquote>
<pre>
if constexpr(!(float_param == float_param)) {
// Handle NaN
}
</pre>
</blockquote>
<h2>References</h2>
<ul>
<li>JF Bastien, <a href="http://wg21.link/p0476">Bit-casting object representations</a> </li>
<li>Jeff Snyder, <a href="http://wg21.link/p0732">Class Types in Non-Type Template Parameters</a> </li>
</ul>
</body>
</html>