New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
remove move constructor for not_null. #842
remove move constructor for not_null. #842
Conversation
Looks good! Thanks Jordan. |
The Travis failure was due to a setup failure. Appveyor, however, was clean so I'm merging this. |
I'm not sure if this commit will have the intended effect. Doesn't the constructor need to be marked 'delete' in order for the compiler to stop auto-generating it? (copy constructor is defaulted, so compiler will assume it can safely generate a move-constructor!). I'm also looking at the move-assignment operator and thinking that it too will still be auto-generated, because the copy-assignment operator is defaulted as well. That said, I've been watching the problem of move constructors in gsl::not_null and its history within the GSL over the last few months, and this looks like no matter what you pick, it's going to be messy either way. Either: A) Disallow move-constructing and move-assigning out of variables, and lose the ability to pass not_null<unique_ptr> and not_null<shared_ptr> by value to functions without having to force people to create l-values on a separate line. Returning not_null<unique_ptr> and not_null<shared_ptr> from a function by value will be almost impossible, with the latter being the only one you'll ever be able to compile, whereas the former with unique_ptr won't! B) Allow move-constructing and move-assigning out of variables, at the expense of allowing developers to cause a not_null to become null. Most of this seems to stem from the fact that the language gives no way for implementations to know at compile-time whether we are "moving to pillage an l-value" vs "moving from a temporary r-value reference". I believe this is the crux of why this problem has perplexed everyone working on this class over the last 2 years. I'd love to know your thinking on this. Personally I'm not sure which of these two is the lesser evil. I'm somewhat inclined to think developers will push for (A) more, but (B) seems like something we'd still want to offer some sort of protection from, at least in contractual places like function calls and function returns via forcing another null check during move-copies and move-assigns.
Maybe if you did this (and implemented A and B), while continuing to support the nullable not_null via move constructors, it might be practically defensive enough to work with that as a known issue? |
Unit tests have been changed for |
@beinhaeter I missed strict_not_null. I'll submit a pull request to align them after I get a chance to discuss @yuri-sevatz's comment with @hsutter and @gdr-at-ms. |
I agree that Yes, an intended effect of this change is that a |
This is wrong. A user-declared copy constructor prevents the implicit declaration of the move constructor. A user-declared copy assignment operator also prevents the implicit declaration of a move constructor. See [class.copy.ctor] p8.
Wrong, for similar reasons. See [class.copy.assign] p4.
No. NEVER delete a move constructor, it prevents you from copying rvalues. Consider: If you delete the move constructor it won't even compile. Never delete a move constructor or move assignment operator. If you want moving to be equivalent to copying then do not write move ctor/assignment, and ensure there is a user-declared copy ctor or copy assignment operator or both (or a user-declared destructor) N.B. user-declared not user-provided, which means #862 should be closed because it's based on nonsense. |
tl;dr what you want is "no move constructor", not "deleted move constructor". |
Those questionable proposals are not necessary. It works perfectly today with a one line change to --- a/include/gsl/pointers
+++ b/include/gsl/pointers
@@ -72,7 +72,7 @@ public:
}
template <typename = std::enable_if_t<!std::is_same<std::nullptr_t, T>::value>>
- constexpr not_null(T u) : ptr_(u)
+ constexpr not_null(T u) : ptr_(std::move(u))
{
Expects(ptr_ != nullptr);
} Thanks to C++17 guaranteed copy elision you can return a What doesn't work is returning the object from You should look into how move-only iterators are now supported by the C++20 ranges library, so that |
Aside: why does that constructor have an |
This would allow --- a/include/gsl/pointers
+++ b/include/gsl/pointers
@@ -72,7 +72,7 @@ public:
}
template <typename = std::enable_if_t<!std::is_same<std::nullptr_t, T>::value>>
- constexpr not_null(T u) : ptr_(u)
+ constexpr not_null(T u) : ptr_(std::move(u))
{
Expects(ptr_ != nullptr);
}
@@ -85,14 +85,14 @@ public:
not_null(const not_null& other) = default;
not_null& operator=(const not_null& other) = default;
- constexpr T get() const
+ constexpr std::conditional_t<std::is_copy_constructible<T>::value, T, const T&> get() const
{
Ensures(ptr_ != nullptr);
return ptr_;
}
constexpr operator T() const { return get(); }
- constexpr T operator->() const { return get(); }
+ constexpr decltype(auto) operator->() const { return get(); }
constexpr decltype(auto) operator*() const { return *get(); }
// prevents compilation when someone attempts to assign a null pointer constant I don't think changing |
If you don't want |
See also #89 |
Should that be a rule in the Core Guidelines? |
Yes, if people are suggesting doing it, I think it's worth clearly stating why it's wrong. |
@jwakely Thanks for this, your changes look right. Thanks! |
Moving not_null will replace whatever pointer is stored within with null, which goes against the purpose of not_null.
This change removes the move constructor for not_null.