implicit vs. explicit deleted copy constructor - c++

I've recently experienced some strange behaviour in my C++11 code.
I've got a class, which is only movable:
class only_move
{
public:
only_move() : value(0) {}
only_move(int value) : value(value) {}
only_move(const only_move&) = delete;
only_move& operator=(const only_move&) = delete;
only_move(only_move&&) = default;
only_move& operator=(only_move&&) = default;
int value;
};
And I've got another class, which contains an only_move object:
class has_only_move_member
{
public:
has_only_move_member() = delete;
has_only_move_member(only_move&& value) : member(std::move(value)) {}
only_move member;
};
If I understood it correct, that means has_only_move_member can't be copied, as the only_move member can't be copied. This means has_only_move_member(const has_only_move_member&) is deleted implicitly. Let's check that out:
has_only_move_member object(only_move(5));
has_only_move_member copy_of_object(object);
As expected, it prints out:
error: use of deleted function ‘has_only_move_member::has_only_move_member(const has_only_move_member&)’
note: ‘has_only_move_member::has_only_move_member(const has_only_move_member&)’ is implicitly deleted because the default definition would be ill-formed:
class has_only_move_member
Okay, so I moved on and put has_only_move_member instances in a std::map. As they don't have a copy constructor, I've moved them into it:
has_only_move_member object(only_move(5));
std::map<int, has_only_move_member> data;
data.emplace(5, std::move(object));
So far so good. This works like a charm.
But I had an idea. What about being more explicit and explicitly deleting the copy constructor of has_only_move_member. So I wrote into the class has_only_move_member:
has_only_move_member(const has_only_move_member&) = delete;
After that, the same code from above, there I moved objects into an map gives me an error:
/usr/include/c++/4.8/bits/stl_pair.h:134:45: error: use of deleted function ‘has_only_move_member::has_only_move_member(const has_only_move_member&)’
Yeah of course it's deleted, but why is there such a gap between implicit and explicit delete?
I'm getting the same behaviour with g++ 4.8.2 and clang 3.4-rc1 on Debian with libstdc++ and recent apple-clang-llvm 4.0 on Mac OS X 10.9 with libc++

I already said this in the comment but since this is a correct answer I am reposting as one:
You added a custom constructor (even though it's a deleted one). Therefore the compiler won't auto generate a move constructor. The compiler then falls back to copying which (as you requested) it obviously can't do.

Related

Attempting to delete an initializer list constructor does not always take effect

Sorry for the generic title, but it's a mindfu*k situation, which I can't easily describe.
Suppose the following code:
struct S
{
S() = default;
int x;
int y;
};
S f()
{
return { 1, 2 };
}
This compiles and works perfectly fine. I want to forbid it, as it's bug prone (the actual code is far more complex). So, I tried adding
template<typename T>
S(std::initializer_list<T>) = delete;
but guess what - nothing changes. Tested on Visual Studio 2019 with std=c++17. The C++ resharper shows this as an error, but msvc actually compiles this and it works.
Wait, now it gets interesting. If S() = default; is replaced with S() {}, the compilation fails with
'S::S<int>(std::initializer_list<int>)': attempting to reference a deleted function
OK, this looks like something to do with user-defined constructors and initialization?! Messy, but kinda understandable.
But wait - it gets even more interesting - keeping the = default constructor, but making the fields private also alters this behavior and guess what - the error has nothing to do with inaccessible members, but it again shows the error from above!
So, in order to make this deletion work, I should either make the fields private or define my own empty constructor (ignore the uninitialized x and y fields, this is just a simplified example), meaning:
struct S
{
S() = default;
// S() {}
template<typename T>
S(std::initializer_list<T>) = delete;
private:
int x;
int y;
};
clang 13 and GCC 11 behave exactly the same way, while GCC 9.3 fails to compile the original code (with =default constructor, public fields, but deleted initializer list constructor).
Any ideas what happens?
In C++17, S is considered an aggregate, and because of that you are not calling any constructor, you are basically directly initializing the members. If you change to using C++20, S is no longer considered an aggregate as the rules were changes and the code will work as expected.
The reason changing the access specifier works is that the access specifier of all non-static data members of an aggregate needs to be public. Having them be non-public means your class is no longer an aggregate, and you no longer get aggregate initialization, but instead it tries to do list initialization and fails for the deleted constructor.

When is the compiler allowed to optimize auto+brace style initialization?

Suppose you have a class called Product, defined like this:
class Product
{
public:
Product(const char *name, int i);
Product(Product &&rhs);
Product(const Product &rhs);
~Product();
private:
const char *m_name;
int m_i;
};
and you initialize a variable like this:
auto p = Product{"abc",123};
I thought that the standard dictated that a compiler must logically do the following:
construct a temporary Product
move-construct p (using the temporary Product)
But that the compiler was allowed to optimize this so that p is directly constructed.
I verified this (Visual Studio 2013) and indeed, the compiler optimizes this, even if we have our own custom (non-default) move-constructor. This is fine.
However, if I explicitly delete the copy- and move-constructor, like this:
class Product
{
public:
Product(const char *name, int i);
Product(Product &&rhs) = delete;
Product(const Product &rhs) = delete;
~Product();
private:
const char *m_name;
int m_i;
};
The auto+brace initialization still compiles. I though the compiler had to prevent this because there is no copy- or move- allowed.
Strange enough, if I make the deleted copy- and move-constructor private, like this:
class Product
{
public:
Product(const char *name, int i);
~Product();
private:
Product(Product &&rhs) = delete;
Product(const Product &rhs) = delete;
const char *m_name;
int m_i;
};
Then the auto+brace initialization doesn't compiler anymore.
error C2248: 'Product::Product' : cannot access private member declared in class 'Product'
Is this expected behavior?
Is this a bug in Visual Studio 2013 (Update 3)?
Note:
I tried compiling this on ideone and there it indeed refuses to compile the initialization when the copy- and move-constructors are deleted (and public). So I think this is a Visual Studio bug.
the standard is very clear as you mentioned before, indicating that this is a bug in the cl-compiler. You never can be sure, though if one compiler is saying something and all others disagree, I expect that this would be one of the many non-standard-compliant implementations of the MSVC compiler.
The interpretation of clang version 3.7 (svn-build):
t.cpp:19:7:{19:11-19:30}: error: call to deleted constructor of 'Product'
[Semantic Issue]
auto p = Product{"abc", 123};
^ ~~~~~~~~~~~~~~~~~~~
t.cpp:8:2: note: 'Product' has been explicitly marked deleted here
[Semantic Issue]
Product(Product &&rhs) = delete;
^
1 error generated.
make: *** [t.o] Error 1
The interpretation of gcc 4.8:
t.cpp: In function ‘int main()’:
t.cpp:19:29: error: use of deleted function ‘Product::Product(Product&&)’
auto p = Product{"abc", 123};
^
t.cpp:8:2: error: declared here
Product(Product &&rhs) = delete;
^
make: *** [build/gcc/t.o] Error 1
Keep also in mind that the Explicitly Defaulted and Deleted Functions are new since MSVC 2013 and the implementation of it is not yet complete. Like it does not yet understand =default for move constructors.
My guess would be that MSVC 2013 does not check for the move constructor or simply falls back to the copy constructor.
It might be interesting to check MSVC 2015, since it appears to have a (more) complete implementation of these constructions.
JVApen
In your line
auto p = Product{"abc",123};
the equal sign is not the denoting the assignment operator, but is just the syntax for an initializer. Hence, the compiler is not optimizing anything, but just doing the initialization.

How do I return a non-movable (but copyable) object?

Edit: End goal: I want to make a container class that never uses move, even when it's available. NonMove is a class of test objects for that container.
I tried different variations, but GCC insists on wanting to use move.
class NonMove {
public:
NonMove() {}
// Copy.
NonMove(const NonMove&) {}
NonMove& operator=(const NonMove&) {}
// Move
NonMove(NonMove&&) = delete;
NonMove& operator=(NonMove&&) = delete;
};
NonMove foo() {
return NonMove();
}
Error with GCC 4.9.1 with -std=gnu++11
move.cc: In function ‘NonMove foo()’:
move.cc:15:18: error: use of deleted function ‘NonMove::NonMove(NonMove&&)’
return NonMove();
^
move.cc:10:3: note: declared here
NonMove(NonMove&&) = delete;
^
End goal: I want to make a container class that never uses [move], even when it's available. NonMove is a class of test objects for that container.
Your class does not necessarily help achieve your goal, because deleting the move constructor means the type is not CopyConstructible, so cannot be used to accurately test a container that only copies.
To prevent moving you can ensure that all potential copies or moves are done with a const source object:
NonMove foo() {
return const_cast<const NonMove&&>(NonMove());
}
The move constructor isn't viable if the RHS is const.
It's usually sufficient to just ensure the RHS is an lvalue, as that won't be moved either, but there is a special case for function return values, which can be moved even if they are lvalues, meaning this would try to use the move constructor if it exists:
NonMove foo() {
NonMove nm;
return nm;
}
Adding const ensures it will be copied instead:
NonMove foo() {
NonMove nm;
return const_cast<const NonMove&>(nm);
}

C++11 array initialization with a non-copyable type with explicit constructor

I have a (third-party) class which is non-copyable. I'd like to initialize an array of them. Here's my best attempt:
#include <array>
class Thing
{
public:
explicit Thing(int) {}
Thing(const Thing&) = delete;
};
int main()
{
std::array<Thing, 1> things{{{100}}}; // error here
};
GCC 4.7.2 says:
error: converting to ‘std::array::value_type
{aka Thing}’ from initializer list would use explicit constructor
‘Thing::Thing(int)’
OK, but that's exactly what I want--to use the explicit constructor. How can I express that? If I actually invoke the constructor myself, then I get an error about the copy constructor being deleted. And I can't use std::move() because Thing is not movable (and I can't modify it).
The only alternative I've found so far is https://stackoverflow.com/a/15962814/4323 but this is undesirable because it's a bunch of extra code plus I need to cast the "storage" everywhere I use it (or keep a separate pointer to it, which adds indirection I don't want).
I want a solution that gives maximum performance when actually using the Things without a lot of ugly boilerplate.
Yet again, C++17's guaranteed copy elision comes to the rescue: an expression like Thing{100} no longer creates an object but merely specifies how some other object (your array element) is to be created.
I tried adding the default move ctor and move assignment operator, changed the initialization a bit and it compiles:
#include <array>
class Thing
{
public:
explicit Thing(int) {}
Thing(const Thing&) = delete;
Thing(Thing&&) = default;
Thing& operator=(Thing&&) = default;
};
int main()
{
std::array<Thing, 1> things {{ Thing(100) }}; // error gone
}
EDIT: I'd missed the "third-party" part. Sorry if this doesn't help :)
You can use a std::vector:
std::vector<Thing> things;
things.reserve(1);
things.emplace_back(100);
or for just one element boost::optional:
boost::optional<Thing> thing;
thing.emplace(100);

non-copyable objects and value initialization: g++ vs msvc

I'm seeing some different behavior between g++ and msvc around value initializing non-copyable objects. Consider a class that is non-copyable:
class noncopyable_base
{
public:
noncopyable_base() {}
private:
noncopyable_base(const noncopyable_base &);
noncopyable_base &operator=(const noncopyable_base &);
};
class noncopyable : private noncopyable_base
{
public:
noncopyable() : x_(0) {}
noncopyable(int x) : x_(x) {}
private:
int x_;
};
and a template that uses value initialization so that the value will get a known value even when the type is POD:
template <class T>
void doit()
{
T t = T();
...
}
and trying to use those together:
doit<noncopyable>();
This works fine on msvc as of VC++ 9.0 but fails on every version of g++ I tested this with (including version 4.5.0) because the copy constructor is private.
Two questions:
Which behavior is standards compliant?
Any suggestion of how to work around this in gcc (and to be clear, changing that to T t; is not an acceptable solution as this breaks POD types).
P.S. I see the same problem with boost::noncopyable.
The behavior you're seeing in MSVC is an extension, though it's documented as such in a roundabout way on the following page (emphasis mine) http://msdn.microsoft.com/en-us/library/0yw5843c.aspx:
The equal-sign initialization syntax is different from the function-style syntax, even though the generated code is identical in most cases. The difference is that when the equal-sign syntax is used, the compiler has to behave as if the following sequence of events were taking place:
Creating a temporary object of the same type as the object being initialized.
Copying the temporary object to the object.
The constructor must be accessible before the compiler can perform these steps. Even though the compiler can eliminate the temporary creation and copy steps in most cases, an inaccessible copy constructor causes equal-sign initialization to fail (under /Za, /Ze (Disable Language Extensions)).
See Ben Voigt's answer for a workaround which is a simplified version of boost::value_initialized, as pointed out by litb in a comment to Ben's answer. The docs for boost::value_initalized has a great discussion of the problem, the workaround, and some of the pitfalls of various compiler issues.
I don't think template metaprogamming is needed. Try
template <class T>
void doit()
{
struct initer { T t; initer() : t() {} } inited;
T& t = inited.t;
...
}
There's §12.8/14:
A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible.
And then there's §12.8/15:
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects.
So, the question is really, if the implementation omits the call to the copy constructor (which it is clearly allowed to do), is the copy constructor actually used?
And, the answer to that is yes, per §3.2/2:
A copy constructor is used even if the call is actually elided by the implementation.
Have you seen what happens when you compile using /Wall with MSVC? It states the following about your class:
nocopy.cc(21) : warning C4625: 'noncopyable' : copy constructor could not be
generated because a base class copy constructor is inaccessible
nocopy.cc(21) : warning C4626: 'noncopyable' : assignment operator could not be
generated because a base class assignment operator is inaccessible
GCC remedy:
create a copy constructor for noncopyable (and an assignment operator ideally!) that does what it can to copy the information from noncopyable_base, namely invoking the constructor for noncopyable_base that has no parameters (since that is the only one accessible by noncopyable) and then copying any data from noncopyable_base. Given the definition of noncopyable_base, however, it seems there is no data to copy, so the simple addition of noncopyable_base() to the initializer list of a new noncopyable(const noncopyable &) function should work.
Take note of what MSVC said about your program though. Also note that if you use T t() rather than T t = T(), another warning (C4930) is generated by MSVC, though GCC happily accepts it either way without any warning issued.