45
struct A
{
    A();

    A(const A&);
    A& operator =(const A&);

    A(A&&) = delete;
    A& operator =(A&&) = delete;
};

struct B
{
    B();

    B(const B&);
    B& operator =(const B&);    
};

int main()
{
    A a;
    a = A(); // error C2280

    B b;
    b = B(); // OK
}

My compiler is VC++ 2013 RC.

error C2280: 'A &A::operator =(A &&)' : attempting to reference a deleted function

I just wonder why the compiler doesn't try A& operator =(const A&); when A& operator =(A&&) is deleted?

Is this behavior defined by the C++ standard?

einpoklum
  • 86,754
  • 39
  • 223
  • 453
xmllmx
  • 33,981
  • 13
  • 121
  • 269
  • 2
    This is described quite well in: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2346.htm#delete – mirk Oct 09 '13 at 07:47
  • 4
    You might also copy the error message, because just `C2280` requires everyone to google that. – Bartek Banachewicz Oct 09 '13 at 07:47
  • Deletion of move constructor implicitly deletes copy constructor and vice versa. – Rapptz Oct 09 '13 at 07:47
  • @mirk great. now the question is why, since the move-assignment operator is deleted and the copy-assignment operator is *specced*, isn't the copy-assignment operator still invoked. – WhozCraig Oct 09 '13 at 07:50
  • 2
    @Rapptz, however, I explicitly defines copy-ctor and copy-assignment. – xmllmx Oct 09 '13 at 07:50
  • 5
    @WhozCraig: "lookup and overload resolution occurs before the deleted definition is noted.". 1) Move assignment is selected by overload resolutions 2) it is noted to be deleted 3) compile fails – mirk Oct 09 '13 at 07:56
  • 2
    @mirk so its noted as declared, but not actually deleted until after overload resolution. does that about sum it up? thus it matches and thus she breaks? ok. that makes perfect sense. – WhozCraig Oct 09 '13 at 08:25

2 Answers2

71
a = A(); // error C2280

The expression on the right is a temporary which means it will look for operator=(A&&) and sees it is deleted. Hence the error. There is no further search.

=delete does not mean "don't use me, instead use next best one". It rather means, "don't use me when you need me — instead be alone in the wild."

Here is another example. If I want the instances of my class X to be created with only long and no other type (even if it converts into long!), then I would declare class X as:

struct X
{
     X(long arg); //ONLY long - NO int, short, char, double, etc!

     template<typename T>
     X(T) = delete;
};

X a(1);  //error - 1 is int 
X b(1L); //ok    - 1L is long

That means, the overload resolution is performed before the compiler sees the =delete part — and thus results in an error because the selected overload is found deleted.

Hope that helps.

Nawaz
  • 327,095
  • 105
  • 629
  • 812
23

When you =delete a function, you actually are deleting its definition.

8.4.3 Deleted definitions [dcl.fct.def.delete]

1 A function definition of the form:

attribute-specifier-seqopt decl-specifier-seqopt declarator = delete ;

is called a deleted definition. A function with a deleted definition is also called a deleted function.

But by doing so, you are also declaring that function. Quoting from the standard [1]:

4 A deleted function is implicitly inline. [ Note: The one-definition rule (3.2) applies to deleted definitions. —end note ] A deleted definition of a function shall be the first declaration of the function [...]

And so by doing a = A(), the compiler actually resolves to A::operator=(A&&) because it has been declared (not A::operator(const A&), because A&& is "more binding" to r-values). However with its definition being deleted, the line is ill-formed.

2 A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.


[1] The tone of the emphasized sentence here is actually imperative. The standard directs that declaring a function =deleted must first appear before other declarations of it. But still, it supports the fact that deleting a function also declares the function.

Mark Garcia
  • 16,438
  • 3
  • 50
  • 93
  • 2
    +1 Furthermore, to accomplish what the OP appears to want, all he must do is declare the user-defined copy-constructor/copy-asignment-operator members. By **C++§ 12.8 [class.copy]**, the presence of a user-defined versions of said-members (copy) squelches the *default* implementations (respectively) of move. I.e. you can only have move *and* copy if you declare both. If the OP wants to evade from move, just declare copy *only*. – WhozCraig Oct 09 '13 at 08:14
  • @WhozCraig: *"I.e. you can only have move and copy if you declare both."*.... Or declare neither (assuming there is no member (such as `std::unique_ptr`) in the class which comes in the way of compiler generated ones). – Nawaz Oct 09 '13 at 08:16
  • 2
    @WhozCraig Though I don't know why the OP would not want to use move semantics. (I'm guilty of this, and in fact, I made a question which I'm not that proud of...) – Mark Garcia Oct 09 '13 at 08:17
  • 1
    @Nawaz yessir. The section cite was in specific reference to when default implementations are *not* generated. There are other conditions, but that one specifically applies to the OP's case, with his user-defined copy-members. Interestingly you can squelch move and still have default copy if you *declare* copy as `= default;` within your class, but remain silent on the matching move-decl presence. I found that rather interesting. (see C++§ 12.8,p20). It specifically states move is not generated because copy has been "user-declared". Interestingly related to the answer in this question =P – WhozCraig Oct 09 '13 at 08:18