Why do C++11-deleted functions participate in overload resolution?

C++C++11

C++ Problem Overview


Why does C++11 make "deleted" functions participate in overload resolution?
Why is this useful? Or in other words, why are they hidden instead of being deleted entirely?

C++ Solutions


Solution 1 - C++

Half of the purpose of the = delete syntax is to be able to prevent people from calling certain functions with certain parameters. This is mainly to prevent implicit conversions in certain specific scenarios. In order to forbid a particular overload, it has to participate in overload resolution.

The answer you cite gives you a perfect example:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

If delete removed the function entirely, that would make the = delete syntax equivalent to this:

struct onlydouble2 {
  onlydouble2(double);
};

You could do this:

onlydouble2 val(20);

This is legal C++. The compiler will look at all constructors; none of them take an integer type directly. But one of them can take it after an implicit conversion. So it'll call that.

onlydouble val(20);

This is not legal C++. The compiler will look at all constructors, including the deleted ones. It will see an exact match, via std::intmax_t (which will exactly match any integer literal). So the compiler will select it and then immediately issue an error, because it selected a deleted function.

= delete means "I forbid this," not merely, "This does not exist." It's a much stronger statement.

> I was asking why the C++ standard says = delete means "I forbid this" instead of "this does not exist"

It's because we don't need special grammar to say "this does not exist." We get this implicitly by simply not declaring the particular "this" in question. "I forbid this" represents a construct that cannot be achieved without special grammar. So we get special grammar to say "I forbid this" and not the other thing.

The only functionality you would gain by having an explicit "this does not exist" grammar would be to prevent someone from later declaring it to exist. And that's just not useful enough to need its own grammar.

> there is otherwise no way to declare that the copy constructor does not exist, and its existence can cause nonsensical ambiguities.

The copy constructor is a special member function. Every class always has a copy constructor. Just as they always have a copy assignment operator, move constructor, etc.

These functions exist; the question is only whether it is legal to call them. If you tried to say that = delete meant that they didn't exist, then the specification would have to explain what it means for a function to not exist. This is not a concept that the specification handles.

If you attempt to call a function that hasn't been declared/defined yet, then the compiler will error. But it will error because of an undefined identifier, not because of a "function doesn't exist" error (even if your compiler reports it that way). Various constructors are all called by overload resolution, so their "existence" is handled in that regard.

In every case, there is either a function declared via identifier, or a constructor/destructor (also declared via identifier, just a type-identifier). Operator overloading hides the identifier behind syntactic sugar, but it's still there.

The C++ specification cannot handle the concept of a "function that does not exist." It can handle an overload mismatch. It can handle an overload ambiguity. But it doesn't know about what isn't there. So = delete is defined in terms of the far more useful "attempts to call this fail" rather than the less useful "pretend I never wrote this line."

And again, re-read the first part. You cannot do that with "function doesn't exist." That's another reason why it's defined that way: because one of the main use cases of the = delete syntax is to be able to force the user to use certain parameter types, to explicitly cast, and so forth. Basically, to foil implicit type conversions.

Your suggestion would not do that.

Solution 2 - C++

The C++ Working Draft 2012-11-02 doesn't provide a rationale behind this rule, just some examples

> 8.4.3 Deleted definitions [dcl.fct.def.delete]
...
3 [ Example: One can enforce non-default initialization and non-integral initialization with

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

> — end example ]
[ Example: One can prevent use of a class in certain new expressions by using deleted definitions of a user-declared operator new for that class.

struct sometype {  
  void *operator new(std::size_t) = delete;  
  void *operator new[](std::size_t) = delete;  
};  
sometype *p = new sometype; // error, deleted class operator new  
sometype *q = new sometype[3]; // error, deleted class operator new[]  

> — end example ]
[ Example: One can make a class uncopyable, i.e. move-only, by using deleted definitions of the copy constructor and copy assignment operator, and then providing defaulted definitions of the move constructor and move assignment operator.

struct moveonly {  
  moveonly() = default;  
  moveonly(const moveonly&) = delete;  
  moveonly(moveonly&&) = default;  
  moveonly& operator=(const moveonly&) = delete;  
  moveonly& operator=(moveonly&&) = default;  
  ~moveonly() = default;  
};  
moveonly *p;  
moveonly q(*p); // error, deleted copy constructor  

> — end example ]

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser541686View Question on Stackoverflow
Solution 1 - C++Nicol BolasView Answer on Stackoverflow
Solution 2 - C++Olaf DietscheView Answer on Stackoverflow