10

In a probject I used code similar to the following:

class C {
public:
    C() {}

    C(const C&) = delete;
};

C f() {
    return C();
}

int main() {
    f();
}

In every previous Visual C++ compiler I used (up to 2013), that has never been a problem. But when I try to compile it with the new Visual C++ 2015 compiler I get the following error:

1>c:\devel\c++11\playground\main.cpp(10): error C2280: 'C::C(const C &)': attempting to reference a deleted function
1>  c:\devel\c++11\playground\main.cpp(6): note: see declaration of 'C::C'

I'm not sure why it previously worked but I assume that because of return value optimization the default constructor was called and not the copy constructor.

Is the code I used even legal C++? And if not, what would be the correct way of implementing this code without requiring a copy constructor for my class C? I could of course use a move constructor but then I assume the code would have never been valid C++ before C++11?

fschoenm
  • 1,391
  • 13
  • 32
  • 1
    If I recall correctly, VS didn't implement ` = delete;` syntax until VS2013, so when you say "In every previous Visual C++ compiler I used (up to 2013), that has never been a problem", do you mean "In VS2013, that was not a problem"? – cpplearner Jul 21 '15 at 13:17
  • 1
    Before VS2013, I simply used a copy constructor declaration without implementation so that it would lead to a linker error if it were actually used. – fschoenm Jul 21 '15 at 13:26

3 Answers3

9

Function arguments and return values are initialized using copy-initialization. Copy-initialization requires copy constructors to be accessible, even if they are elided by (N)RVO:

If T is a class type, and the type of other is different, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution. The result of the conversion, which is a prvalue temporary if a converting constructor was used, is then used to direct-initialize the object. The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • But that would mean that the behavior was always illegal in previous versions of Visual Studio because there was no copy constructor? – fschoenm Jul 21 '15 at 13:13
  • 8
    Visual Studio was never exemplary in following C++ standard. – Maxim Egorushkin Jul 21 '15 at 13:14
  • That's exactly what is happening. The compiler requires the copy constructor to be available but it is never actually called. – fschoenm Jul 21 '15 at 13:28
  • 4
    @fschoenm No, the previous behavior was perfectly legal - you declared the copy constructor, so it was accessible: the return statement using it didn't cause an error. Since the compiler applied the RVO, the constructor wasn't actually called, so the fact that you hadn't provided an implementation didn't matter. Now that you're declaring the copy constructed = delete, it is no longer accessible, hence the error. – Stephen C. Steel Jul 21 '15 at 15:21
  • Oh, this isn't confusing at all. :/ If it is elided out, then it shouldn't cause an error, otherwise this could introduce unexpected bugs. – Adrian Feb 24 '17 at 16:26
  • Ok, seems that to ensure that the move or copy constructor isn't called, delete the move constructor and and define the copy constructor with a `static_assert(false, "Copy constructor called");` in the body. This way you satisfy the requirements but stop it from being copied by accident. – Adrian Feb 24 '17 at 16:39
7

You need to follow the rule of 5. Since you deleted the copy constructor, you need to define your move constructor.

C(C&&) = default;
C& operator=(C&&) = default;

With move constructor - works
Without move constructor - doesn't work, violates rule of 5

Note the above site uses gcc and even it will not compile, so it is not specific to Visual Studio, this is a defined and expected behavior.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • 1
    This explains how to fix it, but not why it occurred in the first place. It could be related to the fact that previous versions of VS were [unable to auto-generate the implicitly declared move-constructor and assignment](https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref). Still, why did the code compile in the first place if there was no copy constructor? – ComicSansMS Jul 21 '15 at 13:11
  • This code would not compile in previous versions. In pre-VS2013 the `delete` keyword did not exist ([to delete a function](http://en.cppreference.com/w/cpp/language/function#Deleted_functions), not to delete a pointer), so it would not have compiled. And in VS2013 it would complain about trying to use a deleted function for the reason I described. – Cory Kramer Jul 21 '15 at 13:13
  • @CoryKramer That's not quite true. I can take the example I posted in my question and compile it in VS2013 while it produces a compile error in VS2015. – fschoenm Jul 21 '15 at 13:30
  • @fschoenm In that case I would modify my comment from "it **would** not compile" to "it **should** not compile", if VS2013 allows the OPs code to compile then it is wrong and non-compliant with the C++11 standard. – Cory Kramer Jul 21 '15 at 13:37
0

Maybe previously it managed to select implicitly generated move constructor for this, but in the VS 2015 the implicit generation of move constructor is blocked by the presence of copy operation(s), which if I recall correctly is standard-compliant behaviour.

So you simply need to define the move constructor yourself, possibly a =default one.

altariste
  • 36
  • 2
  • *"previously it managed to select implicitly generated move constructor for this"* Actually in VS2013 there was no implicitly generated move constructor *at all* (they were non-compliant with the C++11 standard in this regard). They finally added the feature as of VS2015. – Cory Kramer Jul 21 '15 at 13:08
  • Wasnt implicit generation of move operations (and handling =default for them) added in 2013 CTP? If it has been added without poper cooperation with copy operations, then if the original poster used this compiler version, it could explain the change in behaviour – altariste Jul 21 '15 at 13:18