forked from HowardHinnant/papers
-
Notifications
You must be signed in to change notification settings - Fork 5
/
alloc-customization-point.html
287 lines (249 loc) · 12.6 KB
/
alloc-customization-point.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>A Customization Point for domain-specific allocators</title>
<style>
p {text-align:justify}
li {text-align:justify}
blockquote.note
{
background-color:#E0E0E0;
padding-left: 15px;
padding-right: 15px;
padding-top: 1px;
padding-bottom: 1px;
}
tab { padding-left: 4em; }
tab2 { padding-left: 2em; }
ins {color:#00A000}
del {color:#A00000}
</style>
</head>
<body>
<address align=right>
Document number: dxxxx
<br/>
Audience: LEWG
<br/>
<br/>
<a href="mailto:[email protected]">Ville Voutilainen</a><br/>
<a href="mailto:[email protected]">Nina Dinka Ranns</a><br/>
2019-08-02<br/>
</address>
<hr/>
<h1 align=center>A Customization Point for domain-specific allocators</h1>
<h2>Abstract</h2>
<p>
This is a proposal for a new customization point for allocators; namely,
it's a mechanism for adapting legacy allocators into uses-allocator
construction, and allows external trait-adaptation of legacy allocators into
the standard allocator model. Such adaptation allows even legacy
allocators that are raw pointers to be used with uses-allocator construction;
in such cases, there is no converting constructor on the target
legacy allocator type that could be used, because legacy types traffick
in allocator pointers. The customization point provides a mechanism
for converting a standard allocator into the right kind of target
type, including raw pointers.
</p>
<p>
Such a mechanism is thus a significant migration help for
existing code that uses legacy 3rd party allocators, for migrating
such code to use standard allocators and standard-allocator-aware
types. We can use e.g. pmr::vector with such legacy types and legacy
allocators with minimal modifications.
</p>
<h2>tl;dr</h2>
<p>
First, we define a primary template for the customization point:
</p>
<pre><blockquote><code>template <class _Tp, class _Alloc>
struct __domain_allocator_traits {
static constexpr bool value = false;
};
</code></blockquote></pre>
<p>__domain_allocator_traits<class T, class Alloc>::value specifies whether type T supports uses allocator construction with a
domain specific allocator DomainAlloc which can be created from an object of Alloc type. A specialization is required to provide an Alloc to DomainAlloc
convert function if the specialization's ::value is true. </p>
<p>A specialization looks roughly like the following:
</p>
<pre><blockquote><code>namespace std {
template<>
struct __domain_allocator_traits<my_value_type, std::pmr::polymorphic_allocator<>> {
static constexpr bool value = false;
static my_alloc* convert(std::pmr::polymorphic_allocator<> _a);
};
</code></blockquote></pre>
<p>And a real-life specialization looks like the following:
</p>
<pre><blockquote><code>namespace std {
template <class TYPE>
struct __domain_allocator_traits<TYPE, typename bsl::enable_if<
BloombergLP::bslma::UsesBslmaAllocator<TYPE>::value,
std::pmr::polymorphic_allocator<>>::type>
: BloombergLP::bslma::UsesBslmaAllocator<TYPE>::type
{
static BloombergLP::bslma::Allocator* convert(std::pmr::polymorphic_allocator<>a)
{
return dynamic_cast<BloombergLP::bslma::Allocator*>(a.resource());
}
}</code></blockquote></pre>
<p>We also allow for an in-class opt-in by providing a static member function
<tt>domain_alloc_convert</tt> and a nested type <tt>domain_allocator_conversion_source</tt> (Note: names to be bikeshed by LEWG).
</p>
<p>
Finally, we modify uses-allocator construction so that
it detects the presence of <tt>__domain_allocator_traits<T, Alloc>::convert</tt>
, calls it if it's present, and uses the result as an allocator. As with the current implementation of <tt>uses-allocator</tt>
construction, we try leading-allocator convention first and fallback to trailing-allocator convention if unsuccessful.
If the type satisfies <tt>__domain_allocator_traits<T, Alloc>::convert</tt>, we only try <tt>uses-allocator</tt>
construction with the result of <tt>__domain_allocator_traits<T, Alloc>::convert</tt>, there is no fallback to
using original allocator.
</p>
<h2>An alternative way</h2>
<p>
In early stages of prototyping of this proposal, we used
a simpler downcast operator added to polymorphic_allocator.
It looks like this:
</p>
<pre><blockquote><code>template <typename _Target,
typename =
enable_if_t<
__is_dynamic_castable<_Target*, memory_resource*>::value
&& !is_same_v<_Target, memory_resource>>>
operator _Target*() const
{
memory_resource* res = resource();
_Target& ret = dynamic_cast<_Target&apm;>(*res);
return &ret;
}
</code></blockquote></pre>
<p>
This was deemed too fishy (because it's an implicit downcasting
conversion) by some early reviewers. It is, however,
a much simpler solution than a new customization point. In this
alternative approach, uses-allocator construction is not modified,
and all that is needed for legacy types is uses_allocator specializations,
and with the nifty trick we showed above for __domain_allocator_traits,
there needs to be only one (in that case for plain uses_allocator),
even though there's dozens of legacy types that need adaptation.
There is also no need for a new customization point in this alternative
approach.
</p>
<h2>Impact on current usage of uses_allocator</h2>
<p>
To keep the behaviour consistent with uses_allocator_construct, uses_allocator_construction_args,
scoped_allocator_adaptor::construct,polymorphic_allocator::construct and tuple need to change
to accomodate for a domain specific allocator.
</p>
<h2>Should we modify uses_allocator ? </h2>
<p>
We have implemented two approaches:
<ul>
<li>one where uses_allocator is unmodified and does not check __domain_allocator_traits </li>
<li>another where uses_allocator depends on __domain_allocator_traits</li>
</ul>
</p>
<p>
Orginally we kept uses_allocator separate from __domain_allocator_traits in order to
limit the impact of this extension; not modyifying uses_allocator means the set of types
for which <tt>uses_allocator</tt> is true remains the same. There already exists a set of types for which
uses_allocator is false, but for which uses allocator constuction is well defined.</p>
<p>
Where this matters is that a type that makes some sort of a decision
based on whether a template parameter supports allocators may need
to use two traits instead of one to make that decision. Our separately
proposed pmr::optional is such a type. With the first approach,
it needs to use both uses_allocator and __domain_allocator_traits
to decide whether to enable allocator support for the parametered
type. In the second approach, it needs only uses_allocator. Types
like pmr::vector don't care, but types that change their internal
storage and API based on this decision do care.
</p>
</p> On further consideration, we concluded that, if a type can convert from a selected allocator
to a domain specific allocator, and does so by either specialising the __domain_allocator_traits trait
or providing the domain_alloc_convert function, then the intentions is to have this type behave as if it
"uses_allocator" and therefore the definition of <tt>uses_allocator</tt> should take such a conversion into account.
This allows such a type to get all the benefits of being uses_alloctor, not just the uses_alloctor construction.
</p>
<p>
LEWG should decide whether uses_allocator should depend on
__domain_allocator_traits.
</p>
<h2>Proposed wording</h2>
<p>
Insert new section after [allocator.uses.trait]:
</p>
<blockquote>
<ins> <h3>Class template domain_allocator_traits</h3>
[1] <code>namespace std {
template <class T, class Alloc> struct domain_allocator_traits {};
}
</code>
</p>
<p>[2] <tt>domain_allocator_traits</tt> provides means to get uses-allocator construction with allocator types other than the
type specific allocator. Meets the <i>Cpp17BinaryTypeTrait</i> requirements ([meta.rqmts]). </p>
<p>[3] If <tt>domain_allocator_traits<class T, class Alloc>::value</tt> is <tt>true</tt>, static member
<tt>domain_allocator_traits<class T, class Alloc>::convert</tt> shall be a function object.
</p>
<p>[4] Implementation shall provide a definition of <tt>domain_allocator_traits<class T, class Alloc></tt> that is
derived from <tt>true_type</tt> if there exists a function object member <tt>T::domain_alloc_convert</tt> and if the
<i>qualified-id</i> <tt>T::domain_allocator_conversion_source</tt> is valid and denotes a type ([temp.deduct]). If
the implementation provides a definition that is derived from <tt>true_type</tt>, <tt>domain_allocator_traits<class T, class Alloc>::convert</tt>
is defined as a static member function object that invokes <tt>T::domain_alloc_convert</tt>. </p>
</ins>
</blockquote>
<p><br/>Modify 20.10.8.1 uses_allocator trait [allocator.uses.trait]</p>
<blockquote>
<p><tt>template <class T, class Alloc> struct uses_allocator;</tt>
<br/>
<i>#Remarks:</i> Automatically detects whether <tt>T</tt> has a nested allocator_type that is convertible from <tt>Alloc</tt>.
Meets the <i>Cpp17BinaryTypeTrait</i> requirements ([meta.rqmts]). The implementation shall provide a definition that is
derived from <tt>true_type</tt> if<ins>
<br/> - </ins>the <i>qualified-id</i> <tt>T::allocator_type</tt> is valid and denotes a type ([temp.deduct]) and
<tt>is_convertible_v<Alloc, T::allocator_type> != false</tt>,<ins> or
<br/> - if <tt>__domain_allocator_traits<T, Alloc>::value</tt> is <tt>true</tt></ins>
<br/><br/>
<del>otherwise it</del><ins>It</ins> shall be derived from <tt>false_type</tt><ins> otherwise</ins>.
A program may specialize this template to derive from <tt>true_type</tt> for a program-defined type <tt>T</tt> that does not
have a nested <tt>allocator_type</tt> but nonetheless can be constructed with an allocator where either:
<br/>- the first argument of a constructor has type allocator_arg_t and the second argument has type <tt>Alloc</tt> or
<br/>- the last argument of a constructor has type <tt>Alloc</tt>.
</p>
</blockquote>
<p>
Modify [allocator.uses.construction]/p1
<blockquote>
<p>
<i>Uses-allocator construction</i> with allocator <tt>alloc</tt><ins> of type <tt>Alloc</tt></ins> and constructor arguments <tt>args...</tt>
refers to the construction of an object of type <tt>T</tt> such that <tt>alloc</tt><ins> or the result of invoking domain_allocator_traits<class T, class Alloc>::convert with
<tt>alloc</tt></ins> is passed to the constructor of T if T uses an allocator type compatible with <tt>alloc</tt>.
</p></blockquote>
</p>
<p>
Modify [allocator.uses.construction]/p2
<blockquote><p>
The following utility functions support <del>three</del><ins>five</ins> conventions for passing <tt>alloc</tt> to a constructor:
<br/>- If <tt>T</tt> does not use an allocator compatible with <tt>alloc</tt>, then <tt>alloc</tt> is ignored.
<br/><ins>- If <tt>__domain_allocator_traits<T, Alloc>::value</tt> is <tt>true</tt> then
<br/><tab2> - If T has a constructor invocable as <tt>T(allocator_arg, domain_alloc, args...)</tt>, where domain_alloc is
a result of invoking <tt>__domain_allocator_traits<T, Alloc>::convert</tt> with <tt>alloc</tt>, then uses-allocator construction chooses this constructor form.
<br/><tab2> - Otherwise, uses_allocator construction choses <tt>T(args..., domain_alloc)</tt>.</ins>
<br/>- Otherwise...</p></blockquote>
</p>
<p>
Modify [allocator.uses.construction]/p5
<blockquote>
<p>
<i>Returns:</i> A tuple value determined as follows:<br/>
- If <tt>uses_allocator_v<T, Alloc></tt> is <tt>false</tt> and <tt>is_constructible_v<T, Args...></tt> is <tt>true</tt>,
return <tt>forward_as_tuple(std::forward<Args>(args)...)</tt>.<br/>
<ins> - Otherwise, if <tt>__domain_allocator_traits<T, Alloc>::value</tt> is <tt>true</tt> then
<br/><tab2> - If <tt>is_constructible_v<T, allocator_arg_t, const Domainalloc&, Args...> is true, where Domainalloc is the return type of <tt>__domain_allocator_traits<T, Alloc>::convert</tt>
, return <tt>forward_as_tuple(allocator_arg, __domain_allocator_traits<T, Alloc>::convert(alloc),std::forward<Args>(args)...,)</tt>
<br/><tab2>- Otherwise, return forward_as_tuple(std::forward<Args>(args)...,__domain_allocator_traits<T, Alloc>::convert(alloc))</tt>.</ins>.
<br/>- Otherwise...
</p></blockquote>
</p>
</body>
</html>