microsoft / GSL

Guidelines Support Library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

not_null doesn't allow implicit conversion of a unique_pt

BenFrantzDale opened this issue · comments

An rvalue not_null<unique_ptr<Child>> won't convert to a not_null<unique_ptr<Base>> like it does with a unique_ptr or a not_null<shared_ptr<>>: https://godbolt.org/z/o7nGEde3s

I'd expect the rvalue to move its unique_ptr out, leaving it in a moved-from state that, yes, is null, but like stlab::copy_on_write<T> it would be in a state that nobody should be reading from.

It won't allow conversion to raw unique_ptr<Base> either, like std::unique_ptr<Base> b = not_null(std::make_unique<Child>());.

Related: std::unique_ptr<int> p = not_null(std::make_unique<int>()); doesn't compile either. I think it needs

constexpr const T& get() const& { 
    Ensures(ptr_ != nullptr);
    return ptr_;
};
constexpr T&& get() && {
    Ensures(ptr_ != nullptr);
    return std::move(ptr_);
};
constexpr operator const T&() const& { return get(); }
constexpr operator const T&&() && { return std::move(*this).get(); }

although probably more to allow it to convert a not_null<unique_ptr<Child>>&& to unique_ptr<Base>.

Currently this

not_null<std::unique_ptr<Child>> c = not_null(std::make_unique<Child>())
not_null<std::unique_ptr<Base>> b = std::move(c);

won't compile either, and I think that is a good safety net.

I believe it comes down to how safe shall the class be? How many cases of misuse shall it be able to catch and prevent?

Fair enough. I disagree, but I can see the argument for not_null absolutely never being null (even though that limits its utility with std::unique_ptr.

Even so, why not provide operator const T&() const so at least

not_null<std::unique_ptr<Child>> c = not_null(std::make_unique<Child>());
const std::unique_ptr<Child>& b = c;

works? b = c.get() works there but there's no implicit conversion.

Personally I've been swayed (mostly by Sean Parent, I think) toward the view that a moved-from object should be allowed to break its invariants in the sense that the only things that a typical moved-from object should have happen to it are destruction and assignment (and possibly other assignment-like things). Being std::semiregular is nice too, but doesn't make sense for classes like not_null that exist to impose conditions. If I had my druthers, not_null would check its invariant in get() (which it does) and allow implicit conversions where the contained pointer, including operator T&&() && so not_null<unique_ptr<T>> p = foo() would work even without copy elision.

Editors call: not_null<unique_ptr<T>> already doesn't support anything but looking at the object in place. Moving from it to a different object (even of the same type) does not work because then we can't guarantee the source is still "not null" -- in fact it must be null.

Thanks @hsutter. That makes it a non-starter for many of my important uses where I have code that needs to be passed a not_null<unique_ptr<T>> I went with gsl-lite and it works beautifully. I think this is an important use case that needs to be addressed one way or another.

Related: I created not_null<unique_ptr<T>> nn_make_unique<T>(auto&&…) and it’s very useful.