C++ Object Array Initialization without Copy Constructor [duplicate] - c++

I have an uncopiable class. Copying this would be problematic. I want to guarantee that it won't be ever copied, so I made its copy constructor deleted:
class A {
public:
A();
A(const A&) = delete;
};
A fun() {
return A();
};
int main() {
A a = fun();
};
Unfortunately, g++ won't compile this on the reason:
t.cc: In function ‘A fun()’:
t.cc:8:12: error: use of deleted function ‘A::A(const A&)’
return A();
^
t.cc:4:5: note: declared here
A(const A&) = delete;
^
t.cc: In function ‘int main()’:
t.cc:12:13: error: use of deleted function ‘A::A(const A&)’
A a = fun();
^
t.cc:4:5: note: declared here
A(const A&) = delete;
^
But this is a very clear situation where copy elision should be used, so the copy constructor shouldn't be ever called. Why is it so?

Until C++17 copy elision is an optimization the compiler is not required to do, so classes must be copyable since the compiler might want to copy (even if it actually does not). In C++17 copy elision will be guaranteed in many cases and then classes won't need copy ctors.
See also:
http://en.cppreference.com/w/cpp/language/copy_elision
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html
https://herbsutter.com/2016/06/30/trip-report-summer-iso-c-standards-meeting-oulu/
(the bit about "Guaranteed copy elision")
You could perhaps use the old trick of declaring the copy constructor in your class but not actually implement it? That should please the compiler as long as it does not actually invoke the copy ctor. I didn't test that, but I believe it should work for your case until C++17 arrives.

You can't force copy elision (yet) (see other answers).
However, you can provide a default move constructor for your class, this will move (and thus, not copy) the return value if RVO/NRVO is not possible. To do this you should add = default for your move constructors:
class A {
public:
A() = default;
A(const A&) = delete;
A(A&&) = default;
A& operator=(A&&) = default;
};
Example

Return value optimization (RVO and NRVO) does not mean the requirement that the types involved by copyable or movable is dropped. This requirement applies, whether you get RVO or not.
The most likely reason this is so is that copy elision is not (currently) enforced. It is an optimization that may take place, and it would not make sense for code to compile or not based on whether that optimization is applied in a particular implementation.
In C++17, RVO wil be enforced in some circumstances, and the requirements of copyability and movability will be dropped.

Related

How to enforce copy elision, why it won't work with deleted copy constructor?

I have an uncopiable class. Copying this would be problematic. I want to guarantee that it won't be ever copied, so I made its copy constructor deleted:
class A {
public:
A();
A(const A&) = delete;
};
A fun() {
return A();
};
int main() {
A a = fun();
};
Unfortunately, g++ won't compile this on the reason:
t.cc: In function ‘A fun()’:
t.cc:8:12: error: use of deleted function ‘A::A(const A&)’
return A();
^
t.cc:4:5: note: declared here
A(const A&) = delete;
^
t.cc: In function ‘int main()’:
t.cc:12:13: error: use of deleted function ‘A::A(const A&)’
A a = fun();
^
t.cc:4:5: note: declared here
A(const A&) = delete;
^
But this is a very clear situation where copy elision should be used, so the copy constructor shouldn't be ever called. Why is it so?
Until C++17 copy elision is an optimization the compiler is not required to do, so classes must be copyable since the compiler might want to copy (even if it actually does not). In C++17 copy elision will be guaranteed in many cases and then classes won't need copy ctors.
See also:
http://en.cppreference.com/w/cpp/language/copy_elision
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html
https://herbsutter.com/2016/06/30/trip-report-summer-iso-c-standards-meeting-oulu/
(the bit about "Guaranteed copy elision")
You could perhaps use the old trick of declaring the copy constructor in your class but not actually implement it? That should please the compiler as long as it does not actually invoke the copy ctor. I didn't test that, but I believe it should work for your case until C++17 arrives.
You can't force copy elision (yet) (see other answers).
However, you can provide a default move constructor for your class, this will move (and thus, not copy) the return value if RVO/NRVO is not possible. To do this you should add = default for your move constructors:
class A {
public:
A() = default;
A(const A&) = delete;
A(A&&) = default;
A& operator=(A&&) = default;
};
Example
Return value optimization (RVO and NRVO) does not mean the requirement that the types involved by copyable or movable is dropped. This requirement applies, whether you get RVO or not.
The most likely reason this is so is that copy elision is not (currently) enforced. It is an optimization that may take place, and it would not make sense for code to compile or not based on whether that optimization is applied in a particular implementation.
In C++17, RVO wil be enforced in some circumstances, and the requirements of copyability and movability will be dropped.

Enforcing explicitly defaulted special member function generation

In C++11, one can explicitly default a special member function, if its implicit generation was automatically prevented.
However, explicitly defaulting a special member function only undoes the implicit deletion caused by manually declaring some of the other special member functions (copy operations, destructor, etc.), it does not force the compiler to generate the function and the code is considered to be well formed even if the function can't in fact be generated.
Consider the following scenario:
struct A
{
A () = default;
A (const A&) = default;
A (A&&) = delete; // Move constructor is deleted here
};
struct B
{
B () = default;
B (const B&) = default;
B (B&&) = default; // Move constructor is defaulted here
A a;
};
The move constructor in B will not be generated by the compiler, because doing so would cause a compilation error (A's move constructor is deleted). Without explicitly deleting A's constructor, B's move constructor would be generated as expected (copying A, rather than moving it).
Attempting to move such an object will silently use the copy constructor instead:
B b;
B b2 (std::move(b)); // Will call B's copy constructor
Is there a way to force the compiler into either generating the function or issue a compilation error if it can't? Without this guarantee, it's difficult to rely on defaulted move constructors, if a single deleted constructor can disable move for entire object hierarchies.
There is a way to detect types like A. But only if the type explicitly deletes the move constructor. If the move constructor is implicitly generated as deleted, then it will not participate in overload resolution. This is why B is movable even though A is not. B defaults the move constructor, which means it gets implicitly deleted, so copying happens.
B is therefore move constructible. However, A is not. So it's a simple matter of this:
struct B
{
static_assert(is_move_constructible<A>::value, "Oops...");
B () = default;
B (const B&) = default;
B (B&&) = default; // Move constructor is defaulted here
A a;
};
Now, there is no general way to cause any type which contains copy-only types to do what you want. That is, you have to static assert on each type individually; you can't put some syntax in the default move constructor to make attempts to move B fail.
The reason for that has to do in part with backwards compatibility. Think about all the pre-C++11 code that declared user-defined copy constructors. By the rules of move constructor generation in C++11, all of them would have deleted move constructors. Which means that any code of the form T t = FuncReturningTByValue(); would fail, even though it worked just fine in C++98/03 by calling the copy constructor. So the move-by-copy issue worked around this by making these copy instead of moving if the move constructor could not be generated.
But since = default means "do what you would normally do", it also includes this special overload resolution behavior that ignores the implicitly deleted move constructor.

C++ auto with member initializer syntax and deleted copy constructor

class A
{
int a;
public:
A(const A&) = delete;
A& operator=(const A&) = delete;
A() : a {0}
{ }
};
int main()
{
auto a = A {};
}
The above code does not compiles and i get the following error: C2280 'A::A(const A &)': attempting to reference a deleted function
I am using visual studio 2015 compiler. My understanding is with member initialization syntax compiler should directly use the default constructor which is what happens when there is no auto and in main i use A a{}. So i was wondering what is the deal with auto in this case.
auto a = A {};
is only valid if A is copy constructible or move constructible. The fact that you use auto is irrelevant. The same would be true for
A a = A {};
as well.
Declaring a copy constructor – even a deleted one – inhibits implicit declaration of a move constructor so your type A is neither copy constructible nor move constructible. If you add the line
A(A&&) = default;
to the body of A, the code should compile again.
In practice, the compiler will not actually call any copy or move constructor in this case and simply construct the object right in a. But the language rules demand that it must still reject the code which makes sense because whether a piece of code is allowed or not should not depend on an optional compiler optimization.
This behavior will (most likely) change in C++17.
Your understanding is correct, so let's see what's happening here, one step at a time.
A {};
As you said, member initialization syntax, completely kosher here.
auto a = (expression of some kind)
And then you're constructing auto a. After performing type inference, this becomes equivalent to...
A a = (expression of some kind)
Which looks like a copy constructor. Which you deleted.
you should use auto this way:
auto a = new A();
if you don't want to use auto this is c++11 way:
A a{};

why does deleting move constructor cause vector to stop working

If I inhibit the move constructor in a class, I can no longer use it in a vector:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
int i_;
};
int main()
{
std::vector<Foo> foo;
foo.push_back(Foo(1));
}
Why is this so?
Summary
Don't delete the move members.
Assuming your compiler is completely C++11 conforming, then explicitly deleting the move constructor will also implicitly declare the following:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
That is if you declare a move constructor (or move assignment operator), and do not declare copy members, they are implicitly declared as deleted. So your complete class Foo is as if:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
Foo(const Foo&) = delete; // implicitly declared
Foo& operator=(const Foo&) = delete; // implicitly declared
int i_;
};
Now vector<Foo>::push_back(Foo(1)) requires that Foo be MoveConstructible. MoveConstructible could be satisfied by an accessible move constructor, or even by an accessible copy constructor. But Foo has neither. To fix you could:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(const Foo&) = default;
Foo& operator=(const Foo&) = default;
int i_;
};
I.e. default the copy members and remove the deleted move member.
In general it is not a good idea to explicitly delete the move members. If you want a class to be copyable but not "movable", just declare exactly as you would in C++03: declare/define your copy members. You can let the copy members be compiler-generated with = default, and that still counts as a user-declaration. And don't declare move members. Move members that don't exist are not the same as deleted move members.
Deleted move members mean you can not construct a copy of Foo from an rvalue, even if the copy constructor would have worked fine to do so. This is rarely the desired intent.
Even if you want your class to not be copyable nor movable, it is better to just delete the copy members and leave the move members undeclared (meaning they won't exist). If you're ever reviewing code (including your own), and see deleted move members, they are almost certainly incorrect, or at the very best superfluous and confusing.
Some day someone will come up with a good use case for deleted move members. But it will be a rare use case. If you see such a pattern in code, you should expect the code author to have a very good explanation. Otherwise, deleted move members are likely to just be incorrect (at best superfluous). But on the bright side this error will show itself at compile time, instead of at run time (as in your example).
Here is a summary chart of what the compiler will implicitly do when you explicitly declare any of the special members. Those squares colored red represent deprecated behavior.
= default and = delete count as user-declared.
Click here if you would like to view the full slide deck.
You say:
I can no longer use it in a vector:
However, since C++11, requirements for use in a vector are minimal; instead, each vector operation has its own requirements. So, in fact you can use Foo in a vector but you are restricted to operations that have no possibility of requiring the object to be moved or copied.
For example you can write:
std::vector<Foo> w(5);
w.pop_back();
and you can also call w[0].some_member_function(); and so on.
However you cannot write any of the following, due to the way you defined Foo:
std::vector<Foo> w = { 1, 2, 3 }; // Requires moving elements out of initializer_list
w.resize(5); // Resize may mean reallocation which may require elements to be moved to new location
w.push_back(5); // ditto
w.erase(w.begin()); // Erasing an element may require other elements to move to fill its place
w[0] = 1; // operator= is implicitly deleted as described in Howard's answer (but you could explicitly default it to make this work)
If you want to have Foo be copyable, but not movable -- and also have any move scenarios fall back to using a copy -- then the only way to do that is to not declare any move-constructor at all; and force inhibition of the compiler-generated move-constructor by declaring a destructor, copy-constructor, and/or copy-assignment operator as shown in Howard's table.
See the last code sample in Howard's answer for an example of this.
To be clear there are actually several different possible statuses for the move constructor, not all shown in Howard's table:
Defined as deleted, and user-defined
Defined as deleted, and not user-defined (this happens when a member is not movable)
Defaulted and user-defined
Defaulted and not user-defined
User-provided (i.e. user-defined and neither defaulted nor deleted)
Not declared
In cases 1, 3, 4, 5 above, the move constructor is found by overload resolution. In cases 2 and 6 it is not found by overload resolution; and a copy/move scenario such as push_back(Foo(1)); will fall back to a copy constructor (if any).

move ctor of class with a constant data member or a reference member

I have some problems understanding when and if the move constructor or move assignment operator are invoked, in particular in the context of a class with constant data member.
Consider the class
template<typename T> class A {
const*T const P ; // constant data member
explicit A(const*T p) : P(p) { std::cerr<<" ctor: P="<<P<<'\n'; }
void test() const { std::cerr" test: P="<<P<<'\n'; }
// move and copy constructors and assignment operators here
};
and the test program
class B {
int X[100];
A<B> get_a() const { return A<B>(this); }
};
int main() {
B b;
A<B> a = b.get_a(); // which operator/ctor is used for '=' here?
a.test();
}
then the results for compilation are different depending on the definitions provided for the move constructor and move assignment operator in class A<>, but also on compiler.
1 without any further declaration in class A<> (as above), both g++ (4.7.0) and icpc (13.0.1) compile fine (with option -std=c++11) and produce the expected output
ctor: P=0x7fffffffd480
test: P=0x7fffffffd480
2 if I declare
A&A::operator=(A&&) = delete;
A&A::operator=(const A&) = delete;
(which seems sensible in view of the constant data member which must be initialiser-list initialised), but don't provide any further ctor, compilation fails with g++ but is okay wich icpc. If in addition I define either (or both) of
A::A(A&&) = default;
A::A(const A&) = default;
both compilers are happy. However, g++ is not happy with the combination
A::A(A&&) = delete;
A::A(const A&) = default;
while icpc is happy.
3 If I play the same game as in 2, except that A::A(A&&) = default; is replaced by
A::A(A&&a) : P(a.P) { std::cerr<<" move ctor: P="<<P<<'\n'; } // never called?
(and equivalent for A::A(const A&)), the results are completely identical, in particular no output is generated from these explicit move and copy ctors.
So which operator is used for = in main()? (and why is no output produced in the last test?)
And why is this operation allowed here at all, given that A<> has a constant data member (the results are identical if I replace the member const*T const P; with const T&R)?
Finally, in case of the different behaviours of g++ and icpc, which, if any, is correct?
A<B> a = b.get_a();
is not an assignment, but an initialization of a from an rvalue. This syntax should fail under C++0x if
the move constructor is deleted,
the move constructor is declared explicit,
the copy constructor is deleted and no move constructor is defined,
no move constructor is defined and, at the same time the move-assignment is defined or deleted.
Declaration or deletion of the copy assignment operator should not have any influence.
Correction: Different to the copy constructor (which is synthesized even if a user-defined copy assignment operator is provided), the compiler does not synthesize a move constructor if a user-defined move assignment is defined. Hence, the above list should be amended by 4 (which I have done now).
Hence, in my opinion,
in [1] in the question, both compilers behave correctly,
in [2]/1, gcc behaves correctly (move assignment prevents generation of move constructor), icpc is wrong,
in [2]/2, both compilers are correct,
in [2]/3, gcc is correct, since the move constructor is explicitly deleted; icpc is wrong,
[3] is a mystery to me. Are you sure you're correct?