@@ -4,33 +4,44 @@ $(D_S Template Constraints,
4
4
5
5
$(P Templates are normally overloaded and matched based on the
6
6
template arguments being matched to the template parameters.
7
- The template parameters can specify specializations, so that
7
+ The template parameters can specify
8
+ $(DDSUBLINK spec/template, parameters_specialization, specializations), so that
8
9
the template argument must match particular type patterns.
9
10
Similarly, template value arguments can be constrained to
10
11
match particular types.)
11
12
13
+ ---
14
+ template Foo(T) { ... }
15
+ template Foo(T : T*) { ... }
16
+ template Foo(T : T[]) { ... }
17
+
18
+ alias f1 = Foo!(int); // picks Foo(T)
19
+ alias f2 = Foo!(int*); // picks Foo(T : T*)
20
+ alias f3 = Foo!(int[]); // picks Foo(T : T[])
21
+ ---
22
+
12
23
$(P But this has its limitations. Many times there are
13
24
arbitrarily more
14
25
complex criteria for what should be accepted by the template.
15
- This can be used to:
26
+ Such criteria could be to:
16
27
)
17
28
18
29
$(UL
19
30
$(LI more finely discriminate about which
20
31
template gets instantiated for given arguments)
21
- $(LI provides better self-documentation about what
32
+ $(LI provide better self-documentation about what
22
33
characteristics template parameters must have)
23
- $(LI can provide better diagnostics when arguments don't match,
34
+ $(LI provide better diagnostics when arguments don't match,
24
35
rather than an obscure error message based on the irrelevant
25
36
(to the user) internal details of the template implementation)
26
37
)
27
38
28
- $(P Constraints address this by simply providing an
29
- expression that must evaluate at compile time to true
39
+ $(P Constraints address this by simply providing a boolean
40
+ expression that is evaluated at compile- time,
30
41
after the arguments are matched to the parameters.
31
- If it is true, then that template is a valid match for
32
- the arguments, if not, then it is not and is passed over
33
- during overload matching.)
42
+ If that boolean is true, then the template is a valid match for
43
+ the arguments, if not, then the template does not match and
44
+ is passed over during overload matching.)
34
45
35
46
$(P The constraint expression follows the template declaration
36
47
and the $(CODE if) keyword:)
@@ -45,20 +56,27 @@ template Foo(int N)
45
56
46
57
$(P which constrains the template $(CODE Foo) to match only if its
47
58
argument
48
- is an odd integer. Arbitrarily complex criteria can be used, as
59
+ is an odd integer.)
60
+
61
+ $(H2 $(LNAME2 predicates, Predicate Functions))
62
+
63
+ $(P Arbitrarily complex criteria can be used, as
49
64
long as it can be computed at compile time. For example, here's
50
65
a template that only accepts prime numbers:
51
66
)
52
67
68
+ $(RUNNABLE_EXAMPLE_COMPILE
53
69
---
54
70
bool isPrime(int n)
55
71
{
56
72
if (n == 2)
57
73
return true;
74
+ // 0, negative and even numbers are not prime
58
75
if (n < 1 || (n & 1) == 0)
59
76
return false;
60
77
if (n > 3)
61
78
{
79
+ // check possible odd integer denominators
62
80
for (auto i = 3; i * i <= n; i += 2)
63
81
{
64
82
if ((n % i) == 0)
@@ -71,16 +89,22 @@ bool isPrime(int n)
71
89
template Foo(int N)
72
90
if (isPrime(N))
73
91
{
74
- ...
92
+ // ...
75
93
}
76
94
77
- Foo!(5) // ok, 5 is prime
78
- Foo!(6) // no match for Foo
95
+ alias f1 = Foo!(5); // ok, 5 is prime
96
+ //alias f2 = Foo!(6); // error: no match for Foo
97
+ //alias f3 = Foo!(9); // error: no match for Foo
79
98
---
99
+ )
100
+
101
+ $(NOTE `isPrime` is called using $(DDSUBLINK spec/function, interpretation, Compile-Time Function Evaluation).)
80
102
81
- $(P Type constraints can be complex, too. For example, a template
82
- Bar that will accept any floating point type using the traditional
83
- type specializations:
103
+ $(H2 $(LNAME2 is, `is` Expressions))
104
+
105
+ $(P Type constraints can be complex, too. With type specialization alone, a template
106
+ `Bar` that will accept any type that implicitly converts to a built-in floating
107
+ point type must use template overloads:
84
108
)
85
109
---
86
110
template Bar(T:float)
@@ -102,84 +126,127 @@ template Bar(T:real)
102
126
)
103
127
---
104
128
template Bar(T)
105
- if (is(T == float) || is(T == double) || is(T == real))
129
+ if (is(T : float) || is(T : double) || is(T : real))
106
130
{
107
131
...
108
132
}
109
133
---
110
- $(P This can be simplified by using the $(CODE isFloatingPoint)
111
- template in library module $(CODE std.traits):
134
+ $(P Unlike with parameter specialization, types with implicit conversion to
135
+ floating point can be ruled out with a different constraint:)
136
+
137
+ $(RUNNABLE_EXAMPLE_COMPILE
138
+ ---
139
+ template Bar(T)
140
+ if (is(T == float) || is(T == double) || is(T == real))
141
+ {
142
+ // ...
143
+ }
144
+
145
+ alias b1 = Bar!float; // OK
146
+ //alias b2 = Bar!int; // error
147
+ ---
148
+ )
149
+ $(P See $(GLINK2 expression, IsExpression) for more tests.)
150
+
151
+ $(P The above example can be simplified by using the $(CODE isFloatingPoint)
152
+ template in library module $(MREF std, traits):
112
153
)
113
154
---
114
155
import std.traits;
156
+
115
157
template Bar(T)
116
158
if (isFloatingPoint!(T))
117
159
{
118
160
...
119
161
}
120
162
---
121
163
164
+ $(H2 $(LNAME2 traits, `__traits`))
165
+
122
166
$(P Characteristics of types can be tested, such as if
123
- a type can be added:
167
+ a type instance can be added:
124
168
)
125
169
170
+ $(RUNNABLE_EXAMPLE
126
171
---
127
172
// Returns true if instances of type T can be added
128
- template isAddable(T)
129
- {
130
- // Works by attempting to add two instances of type T
131
- const isAddable = __traits(compiles, (T t) { return t + t; });
132
- }
173
+ // Works by attempting to add two instances of type T
174
+ const isAddable(T) = __traits(compiles, (T t) { return t + t; });
133
175
134
- int Foo (T)(T t)
176
+ auto twice (T)(T t)
135
177
if (isAddable!(T))
136
178
{
137
- return 3 ;
179
+ return t + t ;
138
180
}
139
181
182
+ // an addable struct type
140
183
struct S
141
184
{
142
- void opAdd(S s) { } // an addable struct type
185
+ int i;
186
+
187
+ S opBinary(string op : "+")(S s)
188
+ {
189
+ return S(i + s.i);
190
+ }
143
191
}
144
192
145
193
void main()
146
194
{
147
- Foo(4); // succeeds
148
- S s;
149
- Foo(s); // succeeds
150
- Foo ("a"); // fails to match
195
+ assert(twice(4) == 8);
196
+ S s = {2} ;
197
+ assert(twice(s).i == 4);
198
+ //twice ("a"); // fails to match
151
199
}
152
200
---
201
+ )
202
+
203
+ $(P $(DDSUBLINK spec/traits, compiles, `__traits(compiles)`) is
204
+ used to check if a function literal successfully compiles. Other
205
+ expressions can be used instead of a function literal. The expression
206
+ is not evaluated.
207
+ Other compile-time `__traits` $(DDLINK spec/traits, Traits, are available).)
208
+
209
+ $(NOTE The function literal above declares an argument of type
210
+ `T` to obtain an instance of `T` without having to construct it.)
153
211
154
212
$(P Since any expression that can be computed at compile time
155
213
is allowed as a constraint, constraints can be composed:
156
214
)
157
215
158
216
---
159
- int Foo (T)(T t)
217
+ T foo (T)(T t)
160
218
if (isAddable!(T) && isMultipliable!(T))
161
219
{
162
- return 3 ;
220
+ return t + t * t ;
163
221
}
164
222
---
165
223
166
- $(P A more complex constraint can specify a list of operations
167
- that must be doable with the type, such as $(CODE isStack) which
168
- specifies the constraints that a stack type must have:)
224
+ $(P Constraints can deal with multiple parameters:)
169
225
170
- ----
171
- template isStack(T)
226
+ ---
227
+ template Foo(T, int N)
228
+ if (isAddable!(T) && isPrime(N))
172
229
{
173
- const isStack =
174
- __traits(compiles,
175
- (T t)
176
- {
177
- T.value_type v = top(t);
178
- push(t, v);
179
- pop(t);
180
- if (empty(t)) { }
181
- });
230
+ ...
182
231
}
232
+ ---
233
+
234
+ $(P A more complex constraint can specify a list of operations
235
+ that must be doable with the type, such as evaluating template $(CODE isStack) which
236
+ requires that the type has a type property `ValueType`, and that 4 functions
237
+ exist which take an instance of the type, 2 of which return certain
238
+ values:)
239
+
240
+ ----
241
+ const isStack(T) =
242
+ __traits(compiles,
243
+ (T t)
244
+ {
245
+ T.ValueType v = top(t);
246
+ push(t, v);
247
+ pop(t);
248
+ if (empty(t)) { }
249
+ });
183
250
184
251
template Foo(T)
185
252
if (isStack!(T))
@@ -188,17 +255,7 @@ template Foo(T)
188
255
}
189
256
----
190
257
191
- $(P and constraints can deal with multiple parameters:)
192
-
193
- ---
194
- template Foo(T, int N)
195
- if (isAddable!(T) && isprime(N))
196
- {
197
- ...
198
- }
199
- ---
200
-
201
- $(H2 Overloading based on Constraints)
258
+ $(H2 $(LNAME2 overloading, Overloading based on Constraints))
202
259
203
260
$(P Given a list of overloaded templates with the same name,
204
261
constraints act as a yes/no filter to determine the list
@@ -216,6 +273,17 @@ Foo!(3) // instantiates A
216
273
Foo!(64) // instantiates B
217
274
---
218
275
276
+ $(P Note the above 2 templates could be combined using
277
+ $(DDSUBLINK spec/version, staticif, `static if`):)
278
+ ---
279
+ template Foo(int N)
280
+ {
281
+ static if (N & 1)
282
+ // body of A
283
+ else
284
+ // body of B
285
+ }
286
+ ---
219
287
$(P Constraints are not involved with determining which
220
288
template is more specialized than another.
221
289
)
0 commit comments