Recently, I came across this answer which describes how to initialize a std::array
of non-default-constructible elements. I was not so surprised because that answer clearly doesn't do any default-constructing.
Instead, it is constructing a temporary std::array
using aggregate initialization, then moving (if the move constructor is available) or copying into the named variable when the function returns. So we only need either the move constructor or copy constructor to be available.
Or so I thought...
Then came this piece of code which confounded me:
struct foo {
int x;
foo(int x) : x(x) {}
foo() = delete;
foo(const foo&) = delete;
foo& operator=(const foo&) = delete;
foo(foo&&) = delete;
foo& operator=(foo&&) = delete;
};
foo make_foo(int x) {
return foo(x);
}
int main() {
foo f = make_foo(1);
foo g(make_foo(2));
}
All the five special member constructors/operators are explicitly deleted, so now I shouldn't be able to construct my object from a return value, correct?
Wrong.
To my surprise, this compiles in gcc (with C++17)!
Why does this compile? Clearly to return a foo
from the function make_foo()
, we have to construct a foo
. Which means that in the main()
function we are assigning or constructing a foo
from the returned foo
. How is that possible?!
No copy constructor is automatically generated.
In another way, = delete means that the compiler will not generate those constructors when declared and this is only allowed on copy constructor and assignment operator. There is also = 0 usage; it means that a function is purely virtual and you cannot instantiate an object from this class.
Deleted function declaration is a new form of function declaration that is introduced into the C++11 standard. To declare a function as a deleted function, you can append the =delete; specifier to the end of that function declaration. The compiler disables the usage of a deleted function.
A default argument is a value provided in a function declaration that is automatically assigned by the compiler if the calling function doesn't provide a value for the argument. In case any value is passed, the default value is overridden.
Welcome to the wonderful world of guaranteed copy elision (new to C++17. See also this question).
foo make_foo(int x) {
return foo(x);
}
int main() {
foo f = make_foo(1);
foo g(make_foo(2));
}
In all of these cases, you're initializing a foo
from a prvalue of type foo
, so we just ignore all the intermediate objects and directly initialize the outermost object from the actual initializer. This is exactly equivalent to:
foo f(1);
foo g(2);
We don't even consider move constructors here - so the fact that they're deleted doesn't matter. The specific rule is [dcl.init]/17.6.1 - it's only after this point that we consider the constructors and perform overload resolution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With