not_null cleanups and improvements: (#449)

* constexpr all the things.

* remove operator=(const T&)
  * it leaves *this in an invalid state if ensure_invariant fails
  * implicitly converting the T to not_null and then assigning is in every way superior.

* simplify conversion from not_null<U> with constructor delegation.

* remove the converting assignment operator; again let the conversion constructor and self-assignment operator do the work.

* Cover the remaining pointer arithmetic operations as Wakely suggests in issue #447.

* Cleanup not_null conversions from null pointer constants:

  * replace constructor that accepts T with constructor template that accepts U convertible to T

  * remove deleted constructor that accepts int

  * Attempts to initialize with nullptr, 0, 0L, 0LL, etc. all unambiguously select the deleted nullptr_t constructor.
This commit is contained in:
Casey Carter 2017-02-13 11:40:27 -08:00 committed by Neil MacIntosh
parent 6367b42ac5
commit e3fecbd1c5

View File

@ -62,81 +62,63 @@ using owner = T;
// Has zero size overhead over T. // Has zero size overhead over T.
// //
// If T is a pointer (i.e. T == U*) then // If T is a pointer (i.e. T == U*) then
// - allow construction from U* or U& // - allow construction from U*
// - disallow construction from nullptr_t // - disallow construction from nullptr_t
// - disallow default construction // - disallow default construction
// - ensure construction from U* fails with nullptr // - ensure construction from null U* fails
// - allow implicit conversion to U* // - allow implicit conversion to U*
// //
template <class T> template <class T>
class not_null class not_null
{ {
public:
static_assert(std::is_assignable<T&, std::nullptr_t>::value, "T cannot be assigned nullptr."); static_assert(std::is_assignable<T&, std::nullptr_t>::value, "T cannot be assigned nullptr.");
public: template <typename U, typename Dummy = std::enable_if_t<std::is_convertible<U, T>::value>>
not_null(T t) : ptr_(t) { ensure_invariant(); } constexpr not_null(U&& u) : ptr_(std::forward<U>(u)) { Expects(ptr_ != nullptr); }
not_null& operator=(const T& t)
{ template <typename U, typename Dummy = std::enable_if_t<std::is_convertible<U, T>::value>>
ptr_ = t; constexpr not_null(const not_null<U>& other) : not_null(other.get()) {}
ensure_invariant();
return *this;
}
not_null(const not_null& other) = default; not_null(const not_null& other) = default;
not_null& operator=(const not_null& other) = default; not_null& operator=(const not_null& other) = default;
template <typename U, typename Dummy = std::enable_if_t<std::is_convertible<U, T>::value>> constexpr T get() const
not_null(const not_null<U>& other)
{ {
*this = other; Ensures(ptr_ != nullptr);
}
template <typename U, typename Dummy = std::enable_if_t<std::is_convertible<U, T>::value>>
not_null& operator=(const not_null<U>& other)
{
ptr_ = other.get();
return *this;
}
// prevents compilation when someone attempts to assign a nullptr
not_null(std::nullptr_t) = delete;
not_null(int) = delete;
not_null<T>& operator=(std::nullptr_t) = delete;
not_null<T>& operator=(int) = delete;
T get() const
{
#ifdef _MSC_VER
__assume(ptr_ != nullptr);
#endif
return ptr_; return ptr_;
} // the assume() should help the optimizer }
operator T() const { return get(); } constexpr operator T() const { return get(); }
T operator->() const { return get(); } constexpr T operator->() const { return get(); }
bool operator==(const T& rhs) const { return ptr_ == rhs; } // prevents compilation when someone attempts to assign a null pointer constant
bool operator!=(const T& rhs) const { return !(*this == rhs); } not_null(std::nullptr_t) = delete;
private: not_null& operator=(std::nullptr_t) = delete;
T ptr_;
// we assume that the compiler can hoist/prove away most of the checks inlined from this
// function
// if not, we could make them optional via conditional compilation
void ensure_invariant() const { Expects(ptr_ != nullptr); }
// unwanted operators...pointers only point to single objects! // unwanted operators...pointers only point to single objects!
// TODO ensure all arithmetic ops on this type are unavailable not_null& operator++() = delete;
not_null<T>& operator++() = delete; not_null& operator--() = delete;
not_null<T>& operator--() = delete; not_null operator++(int) = delete;
not_null<T> operator++(int) = delete; not_null operator--(int) = delete;
not_null<T> operator--(int) = delete; not_null& operator+=(std::ptrdiff_t) = delete;
not_null<T>& operator+(size_t) = delete; not_null& operator-=(std::ptrdiff_t) = delete;
not_null<T>& operator+=(size_t) = delete; void operator[](std::ptrdiff_t) const = delete;
not_null<T>& operator-(size_t) = delete;
not_null<T>& operator-=(size_t) = delete; private:
T ptr_;
}; };
// more unwanted operators
template <class T, class U>
std::ptrdiff_t operator-(const not_null<T>&, const not_null<U>&) = delete;
template <class T>
not_null<T> operator-(const not_null<T>&, std::ptrdiff_t) = delete;
template <class T>
not_null<T> operator+(const not_null<T>&, std::ptrdiff_t) = delete;
template <class T>
not_null<T> operator+(std::ptrdiff_t, const not_null<T>&) = delete;
} // namespace gsl } // namespace gsl
namespace std namespace std