Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of using delete on a non-member function?

Tags:

c++

c++11

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?

like image 817
Lenz Avatar asked Feb 19 '17 20:02

Lenz


People also ask

When would you use a non-member function?

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.

What will happen if I say delete this?

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.

What does delete in C++ do?

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.

What is a non-member function?

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).


1 Answers

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.


Rejecting undesired implicit conversions

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).

Better diagnostics for users

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_asserted, 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]>.

like image 82
Barry Avatar answered Sep 22 '22 07:09

Barry