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

1

28

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?

Hum answered 19/2, 2017 at 20:56 Comment(1)
You can use it in all cases where you think a function takes a too large set of arguments. So you take the evil arguments and make a deleted function that only matches those, but better. For example, you may want to prevent f(int*) from accepting 0 (to only accept nullptr for null pointers). So you can add a f(int) = delete; overload.Torpedo
D
36

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:

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

Democracy answered 19/2, 2017 at 21:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.