This repository has been archived by the owner on Apr 12, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathnn.hpp
314 lines (270 loc) · 13.7 KB
/
nn.hpp
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#pragma once
/*
* Copyright (c) 2015 Dropbox, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <type_traits>
#include <memory>
#include <functional>
#include <cassert>
#include <cstdlib>
namespace dropbox {
namespace oxygen {
// Marker type and value for use by nn below.
struct i_promise_i_checked_for_null_t {};
static constexpr i_promise_i_checked_for_null_t i_promise_i_checked_for_null {};
// Helper to get the type pointed to by a raw or smart pointer. This can be explicitly
// specialized if need be to provide compatibility with user-defined smart pointers.
namespace nn_detail {
template <typename T> struct element_type { using type = typename T::element_type; };
template <typename Pointee> struct element_type<Pointee *> { using type = Pointee; };
}
template <typename PtrType> class nn;
// Trait to check whether a given type is a non-nullable pointer
template <typename T> struct is_nn : public std::false_type {};
template <typename PtrType> struct is_nn<nn<PtrType>> : public std::true_type {};
/* nn<PtrType>
*
* Wrapper around a pointer that is guaranteed to not be null. This works with raw pointers
* as well as any smart pointer: nn<int *>, nn<shared_ptr<DbxTable>>, nn<unique_ptr<Foo>>,
* etc. An nn<PtrType> can be used just like a PtrType.
*
* An nn<PtrType> can be constructed from another nn<PtrType>, if the underlying type would
* allow such construction. For example, nn<shared_ptr<PtrType>> can be copied and moved, but
* nn<unique_ptr<PtrType>> can only be moved; an nn<unique_ptr<PtrType>> can be explicitly
* (but not implicitly) created from an nn<PtrType*>; implicit upcasts are allowed; and so on.
*
* Similarly, non-nullable pointers can be compared with regular or other non-nullable
* pointers, using the same rules as the underlying pointer types.
*
* This module also provides helpers for creating an nn<PtrType> from operations that would
* always return a non-null pointer: nn_make_unique, nn_make_shared, nn_shared_from_this, and
* nn_addr (a replacement for operator&).
*
* We abbreviate nn<unique_ptr> as nn_unique_ptr - it's a little more readable. Likewise,
* nn<shared_ptr> can be written as nn_shared_ptr.
*
* Finally, we define macros NN_CHECK_ASSERT and NN_CHECK_THROW, to convert a nullable pointer
* to a non-nullable pointer. At Dropbox, these use customized error-handling infrastructure
* and are in a separate file. We've included sample implementations here.
*/
template <typename PtrType>
class nn {
public:
static_assert(!is_nn<PtrType>::value, "nn<nn<T>> is disallowed");
using element_type = typename nn_detail::element_type<PtrType>::type;
// Pass through calls to operator* and operator-> transparently
element_type & operator*() const { return *ptr; }
element_type * operator->() const { return &*ptr; }
// Expose the underlying PtrType
operator const PtrType & () const & { return ptr; }
operator PtrType && () && { return std::move(ptr); }
// Trying to use the assignment operator to assign a nn<PtrType> to a PtrType using the
// above conversion functions hits an ambiguous resolution bug in clang:
// http://llvm.org/bugs/show_bug.cgi?id=18359
// While that exists, we can use these as simple ways of accessing the underlying type
// (instead of workarounds calling the operators explicitly or adding a constructor call).
const PtrType & as_nullable() const & { return ptr; }
PtrType && as_nullable() && { return std::move(ptr); }
// Can't convert to bool (that would be silly). The explicit delete results in
// "value of type 'nn<...>' is not contextually convertible to 'bool'", rather than
// "no viable conversion", which is a bit more clear.
operator bool() const = delete;
// Explicitly deleted constructors. These help produce clearer error messages, as trying
// to use them will result in clang printing the whole line, including the comment.
nn(std::nullptr_t) = delete; // nullptr is not allowed here
nn & operator=(std::nullptr_t) = delete; // nullptr is not allowed here
nn(PtrType) = delete; // must use NN_CHECK_ASSERT or NN_CHECK_THROW
nn & operator=(PtrType) = delete; // must use NN_CHECK_ASSERT or NN_CHECK_THROW
// Semi-private constructor for use by NN_CHECK_ macros.
explicit nn(i_promise_i_checked_for_null_t, const PtrType & arg) : ptr(arg) { assert(ptr); }
explicit nn(i_promise_i_checked_for_null_t, PtrType && arg) : ptr(std::move(arg)) { assert(ptr); }
// Type-converting move and copy constructor. We have four separate cases here, for
// implicit and explicit move and copy.
template <typename OtherType,
typename std::enable_if<
std::is_constructible<PtrType, OtherType>::value
&& !std::is_convertible<OtherType, PtrType>::value
, int>::type = 0>
explicit nn(const nn<OtherType> & other) : ptr(other.operator const OtherType & ()) {}
template <typename OtherType,
typename std::enable_if<
std::is_constructible<PtrType, OtherType>::value
&& !std::is_convertible<OtherType, PtrType>::value
&& !std::is_pointer<OtherType>::value
, int>::type = 0>
explicit nn(nn<OtherType> && other) : ptr(std::move(other).operator OtherType && ()) {}
template <typename OtherType,
typename std::enable_if<
std::is_convertible<OtherType, PtrType>::value
, int >::type = 0>
nn(const nn<OtherType> & other) : ptr(other.operator const OtherType & ()) {}
template <typename OtherType,
typename std::enable_if<
std::is_convertible<OtherType, PtrType>::value
&& !std::is_pointer<OtherType>::value
, int>::type = 0>
nn(nn<OtherType> && other) : ptr(std::move(other).operator OtherType && ()) {}
// A type-converting move and copy assignment operator aren't necessary; writing
// "base_ptr = derived_ptr;" will run the type-converting constructor followed by the
// implicit move assignment operator.
// Two-argument constructor, designed for use with the shared_ptr aliasing constructor.
// This will not be instantiated if PtrType doesn't have a suitable constructor.
template <typename OtherType,
typename std::enable_if<
std::is_constructible<PtrType, OtherType, element_type *>::value
, int>::type = 0>
nn(const nn<OtherType> & ownership_ptr, nn<element_type *> target_ptr)
: ptr(ownership_ptr.operator const OtherType & (), target_ptr) {}
// Comparisons. Other comparisons are implemented in terms of these.
template <typename L, typename R>
friend bool operator==(const nn<L> &, const R &);
template <typename L, typename R>
friend bool operator==(const L &, const nn<R> &);
template <typename L, typename R>
friend bool operator==(const nn<L> &, const nn<R> &);
template <typename L, typename R>
friend bool operator<(const nn<L> &, const R &);
template <typename L, typename R>
friend bool operator<(const L &, const nn<R> &);
template <typename L, typename R>
friend bool operator<(const nn<L> &, const nn<R> &);
// ostream operator
template <typename T>
friend std::ostream & operator<<(std::ostream &, const nn<T> &);
template <typename T = PtrType>
element_type * get() const { return ptr.get(); }
private:
// Backing pointer
PtrType ptr;
};
// Base comparisons - these are friends of nn<PtrType>, so they can access .ptr directly.
template <typename L, typename R>
bool operator==(const nn<L> & l, const R & r) { return l.ptr == r; }
template <typename L, typename R>
bool operator==(const L & l, const nn<R> & r) { return l == r.ptr; }
template <typename L, typename R>
bool operator==(const nn<L> & l, const nn<R> & r) { return l.ptr == r.ptr; }
template <typename L, typename R>
bool operator<(const nn<L> & l, const R & r) { return l.ptr < r; }
template <typename L, typename R>
bool operator<(const L & l, const nn<R> & r) { return l < r.ptr; }
template <typename L, typename R>
bool operator<(const nn<L> & l, const nn<R> & r) { return l.ptr < r.ptr; }
template <typename T>
std::ostream & operator<<(std::ostream & os, const nn<T> & p) { return os << p.ptr; }
#define NN_DERIVED_OPERATORS(op, base) \
template <typename L, typename R> \
bool operator op(const nn<L> & l, const R & r) { return base; } \
template <typename L, typename R> \
bool operator op(const L & l, const nn<R> & r) { return base; } \
template <typename L, typename R> \
bool operator op(const nn<L> & l, const nn<R> & r) { return base; }
NN_DERIVED_OPERATORS(>, r < l)
NN_DERIVED_OPERATORS(<=, !(l > r))
NN_DERIVED_OPERATORS(>=, !(l < r))
NN_DERIVED_OPERATORS(!=, !(l == r))
#undef NN_DERIVED_OPERATORS
// Convenience typedefs
template <typename T> using nn_unique_ptr = nn<std::unique_ptr<T>>;
template <typename T> using nn_shared_ptr = nn<std::shared_ptr<T>>;
template <typename T, typename... Args>
nn_unique_ptr<T> nn_make_unique(Args &&... args) {
return nn_unique_ptr<T>(i_promise_i_checked_for_null,
std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
}
template <typename T, typename... Args>
nn_shared_ptr<T> nn_make_shared(Args &&... args) {
return nn_shared_ptr<T>(i_promise_i_checked_for_null,
std::make_shared<T>(std::forward<Args>(args)...));
}
template <typename T>
class nn_enable_shared_from_this : public std::enable_shared_from_this<T> {
public:
using std::enable_shared_from_this<T>::enable_shared_from_this;
nn_shared_ptr<T> nn_shared_from_this() {
return nn_shared_ptr<T>(i_promise_i_checked_for_null, this->shared_from_this());
}
nn_shared_ptr<const T> nn_shared_from_this() const {
return nn_shared_ptr<const T>(i_promise_i_checked_for_null, this->shared_from_this());
}
};
template <typename T>
nn<T*> nn_addr(T & object) {
return nn<T*>(i_promise_i_checked_for_null, &object);
}
template <typename T>
nn<const T*> nn_addr(const T & object) {
return nn<const T*>(i_promise_i_checked_for_null, &object);
}
/* Non-nullable equivalents of shared_ptr's specialized casting functions.
* These convert through a shared_ptr since nn<shared_ptr<T>> lacks the ref-count-sharing cast
* constructor, but thanks to moves there shouldn't be any significant extra cost. */
template <typename T, typename U>
nn_shared_ptr<T> nn_static_pointer_cast(const nn_shared_ptr<U> & org_ptr) {
auto raw_ptr = static_cast<typename nn_shared_ptr<T>::element_type *>(org_ptr.get());
std::shared_ptr<T> nullable_ptr(org_ptr.as_nullable(), raw_ptr);
return nn_shared_ptr<T>(i_promise_i_checked_for_null, std::move(nullable_ptr));
}
template <typename T, typename U>
std::shared_ptr<T> nn_dynamic_pointer_cast(const nn_shared_ptr<U> & org_ptr) {
auto raw_ptr = dynamic_cast<typename std::shared_ptr<T>::element_type *>(org_ptr.get());
if (!raw_ptr) {
return nullptr;
} else {
return std::shared_ptr<T>(org_ptr.as_nullable(), raw_ptr);
}
}
template <typename T, typename U>
nn_shared_ptr<T> nn_const_pointer_cast(const nn_shared_ptr<U> & org_ptr) {
auto raw_ptr = const_cast<typename nn_shared_ptr<T>::element_type *>(org_ptr.get());
std::shared_ptr<T> nullable_ptr(org_ptr.as_nullable(), raw_ptr);
return nn_shared_ptr<T>(i_promise_i_checked_for_null, std::move(nullable_ptr));
}
} } /* end namespace dropbox::oxygen */
namespace std {
template <typename T>
struct hash<::dropbox::oxygen::nn<T>> {
using argument_type = ::dropbox::oxygen::nn<T>;
using result_type = size_t;
result_type operator()(const argument_type & obj) const {
return std::hash<T>{}(obj.as_nullable());
}
};
}
/* These have to be macros because our internal versions invoke other macros that use
* __FILE__ and __LINE__, which we want to correctly point to the call site. We're looking
* forward to std::source_location :)
*
* The lambdas ensure that we only evaluate _e once.
*/
#include <stdexcept>
// NN_CHECK_ASSERT takes a pointer of type PT (e.g. raw pointer, std::shared_ptr or std::unique_ptr)
// and returns a non-nullable pointer of type nn<PT>.
// Triggers an assertion if expression evaluates to null.
#define NN_CHECK_ASSERT(_e) (([&] (typename std::remove_reference<decltype(_e)>::type p) { \
/* note: assert() alone is not sufficient here, because it might be compiled out. */ \
assert(p && #_e " must not be null"); \
if (!p) std::abort(); \
return dropbox::oxygen::nn<typename std::remove_reference<decltype(p)>::type>( \
dropbox::oxygen::i_promise_i_checked_for_null, std::move(p)); \
})(_e))
// NN_CHECK_THROW takes a pointer of type PT (e.g. raw pointer, std::shared_ptr or std::unique_ptr)
// and returns a non-nullable pointer of type nn<PT>.
// Throws if expression evaluates to null.
#define NN_CHECK_THROW(_e) (([&] (typename std::remove_reference<decltype(_e)>::type p) { \
if (!p) throw std::runtime_error(#_e " must not be null"); \
return dropbox::oxygen::nn<typename std::remove_reference<decltype(p)>::type>( \
dropbox::oxygen::i_promise_i_checked_for_null, std::move(p)); \
})(_e))