Excerpt from the Standard 20.12 [function.objects] :
template <class T> reference_wrapper<T> ref(T&) noexcept; template <class T> reference_wrapper<const T> cref(const T&) noexcept; template <class T> void ref(const T&&) = delete; template <class T> void cref(const T&&) = delete;
I am used to seeing =delete
in the context of member functions. The intention is to prohibit an operation that was supplied by the compiler. For example, to make a class non-copyable or non-movable.
In this context, however, the intention appears to be the documentation of the intent. Is this right? Are there any other cases where using a =delete
on a non-member function is desirable, preferable or inevitable?
Use a nonmember function if you don't need type conversion in the first argument or don't need access to private data. Use a member function if you do need access to private data.
Using delete this; is fine. Doing it in the constructor as well as accessing fields of the objects after it is undefined behavior. Sadly this doesn't typically cause a processor fault, just heap corruption.
delete keyword in C++ Delete is an operator that is used to destroy array and non-array(pointer) objects which are created by new expression. New operator is used for dynamic memory allocation which puts variables on heap memory. Which means Delete operator deallocates memory from heap.
Non-member Function: The function which is declared outside the class is known as the non-member function of that class. Below is the difference between the two: The member function can appear outside of the class body (for instance, in the implementation file).
There are two general reasons that I know of to explicitly delete
free functions: to reject undesired implicit conversions, and to provide a better error experience for users.
One useful feature of const
is that temporaries can bind to references to const
. So this works:
void foo(const int& ); foo(42); // ok
That temporary 42
is bound to the reference parameter of the function, and its lifetime is tied to that reference parameter.
Now, consider std::cref()
.The goal is to pass through this reference_wrapper
to somewhere, so we need the underlying reference to stay alive. If we just had this overload:
template <class T> reference_wrapper<const T> cref(const T&) noexcept;
Then I could write std::cref(42)
. That would work fine, I would get back a std::reference_wrapper<const int>
- except it would be a dangling reference. There is no possible way for that code to ever work.
In an effort to fix that obvious bug, we have this overload as well:
template <class T> void cref(const T&&) = delete;
That is, we are explicitly deleting (or defining as deleted) an overloading taking any rvalue. Now, when doing overload resolution, this 2nd overload is preferred when I pass in an rvalue, and that overload is ill-formed, and the compiler informs us of our bug (silly me, I can't do cref(42)
!) instead of me having to spend a few hours with gdb trying to figure out why I don't have an object.
Other examples in the standard library are:
std::as_const()
std::addressof()
std::regex_match()
and std::regex_search()
(#6 accepts lvalue strings, #7 rejects rvalue strings).A different class of example might be to provide a better diagnostic for constrained functions. Let's say I have a function that is only meaningful for integral types:
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0> void foo(T);
And I try to invoke it with a non-integral type:
foo(4.2); // error: no matching function
This fails, as desired. But the error you get isn't super meaningful. Especially if there's other overloads. With Concepts, this'll be better - hopefully, but not necessarily.
But if I add the converse explicitly deleted overload:
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0> void foo(T); template <typename T, std::enable_if_t<!std::is_integral_v<T>, int> = 0> void foo(T) = delete; foo(4.2); // error: use of deleted function
This is more explicit and direct. Especially if the author of foo
provides a comment indicating why this is important. Basically, this is still SFINAE friendly while also giving the benefit of a static_assert
directly indicating failure (whereas if we just static_assert
ed, we'd get a clearer message, but we'd lose SFINAE-friendliness).
Indeed, the motivation of N4186 was to make that comment part of the code itself (though this proposal was rejected). The example in that paper was:
template <typename T> enable_if_t<has_compatible_vector_size<simd_float, T>::value, simd_float> operator+(simd_float, T); template <typename T> enable_if_t<!has_compatible_vector_size<simd_float, T>::value, simd_float> operator+(simd_float, T) = delete;
An example in the standard library is std::make_unique()
for make_unique<U[N]>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With