97

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?

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886

2 Answers2

129

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • You completely avoided answering the question. Obviously I understood the meaning of `= delete` before posting this question; that wasn't what I was asking about... I was asking ***why*** the C++ standard says `= delete` means "I forbid this" instead of "this does not exist" -- note that I asked *"why is this useful?"*, not *"what does this mean?"*. Your answer only tells me what the compiler *does* when it sees `delete`, not the rationale behind its meaning. – user541686 Dec 29 '12 at 22:03
  • Okay, now this is answering the question, but the reason doesn't make sense -- you say, *It's because we don't need special grammar to say "this does not exist",* but that is precisely what the question I linked to needs -- there is otherwise no way to declare that the copy constructor does not exist, and its existence can cause nonsensical ambiguities. What makes you think *"we get this implicitly by simply not declaring the particular "this" in the question"*? You can't do that with a copy constructor and such. – user541686 Dec 30 '12 at 01:17
  • @Mehrdad: See my edit. In brief, copy constructors are special; they have to exist. Your version of =delete syntax would break the spec. – Nicol Bolas Dec 30 '12 at 02:29
  • 1
    @Mehrdad: If you need more clarification on why =delete doesn't work the way you want, you need to be more clear on the *exact* semantics you think =delete should have. Should it be "pretend I never wrote this line?" Or should it be something else? – Nicol Bolas Dec 30 '12 at 02:30
  • 1
    No, I mean I expected `= delete` to mean "this member does not exist", which would imply it could not participate in overload resolution. – user541686 Dec 30 '12 at 04:44
  • 6
    @Mehrdad: And that goes back to my original point, which is why I posted it: if `= delete` meant "this member does not exist", then the first example I posted wouldn't be able to prevent people from passing integers to `onlydouble`'s constructor, since the `onlydouble` overload that is deleted *would not exist*. It wouldn't participate in overload resolution, and therefore it wouldn't prevent you from passing integers. Which is half of the point of the `= delete` syntax: to be able to say, "You cannot pass X implicitly to this function." – Nicol Bolas Dec 30 '12 at 14:39
  • So you're saying it would be impossible to prevent `int`s from being passed as `double`'s in your original example? That's true, but why would that have posed any problem? Even before C++11 you could easily have said `private: onlydouble(std::intmax_t) { throw "not supported"; }` and it would've *hidden* the function, while still allowing it to participate in overloading... so why would there have been a problem to allow `delete` to mean "this member does not exist"? – user541686 Dec 31 '12 at 21:28
  • 3
    @Mehrdad: By that logic, why do you need `=delete` at all? After all, we can say "non-copyable" by doing the exact same thing: declaring the copy constructor/assignment private. Also, note that declaring something private doesn't make it uncallable; code *within* the class can still call it. So it's not the same as `= delete`. No, the `= delete` syntax allows us to do something that was highly inconvenient and inscrutable before in a much more obvious and reasonable way. – Nicol Bolas Dec 31 '12 at 22:10
  • *"By that logic, why do you need `= delete` at all?"* I guess that's precisely the question that's been baffling me the whole time. I used to think we needed `delete` because previously it was impossible to actually *delete* a member, but when I found out `delete` is really just another way of saying "hidden", I posted this question to figure out why it behaves that way... and your question of "why do we even need delete at all?" nails down the question I've been struggling with: why does `delete` hide a member instead of deleting it, when the former was formerly possible, but the latter not? – user541686 Dec 31 '12 at 22:18
  • 2
    @Mehrdad: Because the latter *is possible*: it's called "don't declare it". Then it won't exist. As to why we need syntax to hide an overload rather than abusing private... are you really asking [why we should have a means to explicitly state something, rather than abusing some other feature to get the same effect](http://stackoverflow.com/a/9458874/734069)? It's simply more obvious to anyone reading the code what is going on. It makes the code more easily comprehensible to the user and makes it easier to write, while also fixing problems in the workaround. We don't *need* lambdas either. – Nicol Bolas Dec 31 '12 at 22:52
  • *"Because the latter is possible"*... but it isn't always -- not when the compiler has already declared the function, right? – user541686 Dec 31 '12 at 23:01
  • @Mehrdad: I covered that in my answer. It's a special member function; it *has to exist*. It can't not exist. Another reason why `= delete` is not "this doesn't exist" but "this is forbidden." – Nicol Bolas Dec 31 '12 at 23:07
  • Your logic seems circular to me... you say it "has" to exist, but the only reason I see why it "has" to exist is simply that there is no mechanism to actually delete it. (You haven't given any other reason, and that's as far as my eye can see... but please correct me if there is one.) I simply don't understand how that answers the question of why `delete` hides a function instead of actually deleting it, because if it *did* delete the function, then it could very well delete the copy constructor like any other function... your answer of "it can't" seems to be begging the very question I asked. – user541686 Jan 01 '13 at 00:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/21971/discussion-between-nicol-bolas-and-mehrdad) – Nicol Bolas Jan 01 '13 at 00:25
  • `intmax_t` does not "exactly match any integer literal". That `(20)` example is ambiguous. – T.C. Feb 15 '18 at 03:25
10

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 ]

Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198