aboutsummaryrefslogtreecommitdiff
path: root/include/proj/nn.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/proj/nn.hpp')
-rw-r--r--include/proj/nn.hpp385
1 files changed, 385 insertions, 0 deletions
diff --git a/include/proj/nn.hpp b/include/proj/nn.hpp
new file mode 100644
index 00000000..4b17a17d
--- /dev/null
+++ b/include/proj/nn.hpp
@@ -0,0 +1,385 @@
+#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 <cassert>
+#include <cstdlib>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+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
+ //PROJ_DLL ~nn();
+
+ // Semi-private constructor for use by NN_CHECK_ macros.
+ explicit nn(i_promise_i_checked_for_null_t, const PtrType &arg) noexcept : ptr(arg) {
+ }
+ explicit nn(i_promise_i_checked_for_null_t, PtrType &&arg) noexcept
+ : ptr(std::move(arg)) {
+ }
+
+ // 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))