C++11 non-static member initializers and deleted copy constructor - c++

I'm trying to compile the following simple code with GCC 4.7.2 (MinGW). Here I'm using C++11 feature - non-static member initializers:
#include <iostream>
using namespace std;
struct A
{
int var;
A()
{
cout << "A()\n";
}
A(int i)
{
cout << "A(int i)\n";
var = i;
}
A(const A&) = delete;
};
struct B
{
A a = 7;
};
int main()
{
B b;
cout << "b.a.var = " << b.a.var;
return 0;
}
This code doesn't compile because of deleted copy-constructor which isn't necessary here. Here are errors:
main.cpp:27:11: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
main.cpp: In constructor 'constexpr B::B()':
main.cpp:25:8: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
If I implement copy-constructor like this:
A(const A& a)
{
cout << "A(const A&)\n";
var = a.var;
}
Then code compiles fine and program gives me expected output:
A(int i)
b.a.var = 7
So it means that copy constructor is not used, but why I can't delete it?
Edit: Thanks for your answers. Copy or move constructor is required by standard if I'm using =. To fix this problem I need to implement move constructor or use direct initialization syntax A a{7}.

The initialiser for a gives you copy-initialization:
A a = 7;
For such a copy-initialization, where a user-defined conversion is required, the resulting initialisation is equivalent to:
A a(A(7));
That is, a temporary A is constructed and then passed to the copy constructor of your a object. This copying may be elided, but the copy constructor must be available nonetheless. In other words, the copying can only be elided if the copy would be possible in the first place. If you delete the copy constructor, the copying is impossible.
You'll have a better time with your deleted copy constructor if you do the following:
A a{7};
This does direct-initialization and no copy constructor is required.

Copy initialization is allowed to elide the copy but the copy constructor is mandated to be accessible by the standard.

Per Paragraph 12.2/14 of the C++11 Standard:
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]
The reason why your copy-initialization doesn't compile is that during copy-initialization a temporary object needs to be created (at least logically), and the object being initialized shall be constructed from it.
Now all the previous answers seem to focus just on copy-constructors, but the first problem here is the absence of a move-constructor. As long as you provide one, then it is true that the copy constructor is not necessary.
Alas, deleting the copy constructor prevents the generation of an implicit move constructor. Adding one explicitly would fix the problem:
struct A
{
int var;
A()
{
cout << "A()\n";
}
A(int i)
{
cout << "A(int i)\n";
var = i;
}
A(const A&) = delete;
// THIS MAKES IT WORK
A(A&& a)
{
cout << "A(A&&)\n`;
var = a.var;
}
};
Notice that when both the move-constructor and the copy-constructor are present, the move-constructor is preferred, because the temporary created for copy-initializing your object is an rvalue.
When a move-constructor is absent, the compiler can invoke the copy constructor to perform the initialization, because constant lvalue references can bind to rvalue references and copy is seen as an unoptimized move.
However, even though the compiler is allowed to elide the call to the move or the copy constructor, the semantics of the operation must still be checked. Per Paragraph 12.8/32 of the C++11 Standard:
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ] [...]
Therefore, an error is issued by the compiler if neither the move constructor nor the copy constructor are present.
If you want, however, you can direct-initialize your object rather than copy-initializing it. Just use the direct-initialization syntax instead:
struct B
{
A a{7};
};
This will make the move-constructor and copy-constructor unnecessary, because no temporary is created when direct-initializing an object.

This code doesn't compiles because of deleted copy-constructor which isn't necessary here
Sorry, but your copy constructor is necessary. Even though the copy can be optimised out, it must still be possible in the code. This mandated by the language.

So it means that copy constructor is not used, but whyI can't delete it?
In your case, copy constructor is used only for semantic-check as required by the Standard, it also needs to be accessible. Later on, the compiler optimizes the code, eliding the call to the copy-constructor, so it is not actually invoked.

Related

std::optional: Not participating in overload resolution vs. being defined as deleted

I am trying to understand the mechanism behind type traits propagation as described for std::optional in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0602r4.html. There is a subtle difference in the treatment of copy operations, which shall be conditionally defined as deleted, versus move operations, which shall rather not participate in overload resolution.
What is the reason for that difference, and how would I test the latter? Example:
#include <type_traits>
#include <optional>
struct NonMoveable {
NonMoveable() = default;
NonMoveable(NonMoveable const&) = default;
NonMoveable(NonMoveable&&) = delete;
NonMoveable& operator=(NonMoveable const&) = default;
NonMoveable& operator=(NonMoveable&&) = delete;
};
// Inner traits as expected
static_assert(!std::is_move_constructible<NonMoveable>::value);
static_assert(!std::is_move_assignable<NonMoveable>::value);
// The wrapper is moveable, via copy operations participating in
// overload resolution. How to verify that the move operations don't?
static_assert(std::is_move_constructible<std::optional<NonMoveable>>::value);
static_assert(std::is_move_assignable<std::optional<NonMoveable>>::value);
int main(int argc, char* argv[])
{
NonMoveable a1;
NonMoveable a2{std::move(a1)}; // Bad, as expected
std::optional<NonMoveable> b1;
std::optional<NonMoveable> b2{std::move(b1)}; // Good, see above. But
// useless as a test for
// P0602R4.
return 0;
}
Bonus Question
Does GCC do the right thing? I have modified the example a bit to get a tiny step closer: https://godbolt.org/z/br1vx1. Here I made the copy operations inaccessible by declaring them private. GCC-10.2 with -std=c++20 now fails the static asserts and complains
error: use of deleted function 'std::optional<NonMoveable>::optional(std::optional<NonMoveable>&&)'
According to Why do C++11-deleted functions participate in overload resolution? the delete is applied after overload resolution, which could indicate that the move constructor participated, despite P0602R4 said it shall not.
On the other hand https://en.cppreference.com/w/cpp/language/overload_resolution states right in the beginning
... If these steps produce more than one candidate function, then overload resolution is performed ...
so overload resolution was skipped, because the move constructor was the only candidate?
std::optional is a red herring; the key is understanding the mechanisms that lead to why these requirements are placed on a library type
There is a subtle difference in the treatment of copy operations, which shall be conditionally defined as deleted, versus move operations, which shall rather not participate in overload resolution.
The under-the-hood requirements (and how to implement these) for std::optional are complex. However, the requirement that move operations shall not participate in overload resolution (for non-movable types) vs copy operations being deleted (for non-copyable types) likely relates to a separate topic;
NRVO(1) (a case of copy elision, if you may) and
more implicit moves, e.g. choosing move constructors over copy constructors when returning named objects with automatic storage duration from a function.
We can understand this topic by looking at simpler types than std::optional.
(1) Named Returned Value Optimization
TLDR
The move eagerness that is expanding in C++ (more eager moves in C++20) means there are special cases where a move constructor will be chosen over a copy constructor even if the move constructor has been deleted. The only way to avoid these for, say, non-movable types, is to make sure the type has no move constructor nor a move assignment operator, by knowledge of the rule of 5 and what governs whether these are defined implicitly.
The same preference does not exist for copying, and there would be no reasons to favour removing these over deleting them, if this was even possible (2). In other words, the same kinks that exist for favouring moves in overload resolution, that sometimes unexpectedly choose move over copy, is not present for the reverse; copy over move.
(2) There is no such thing as a class without the existence of a copy ctor and a copy assignment operator (although these may be defined as deleted).
Implicit move eagerness
Consider the following types:
struct A {
A() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
A(A const &) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
A &operator=(A const &) {
std::cout << __PRETTY_FUNCTION__ << "\n";
return *this;
}
};
struct B {
B() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
B(B const &) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
B &operator=(B const &) {
std::cout << __PRETTY_FUNCTION__ << "\n";
return *this;
}
B(B &&) = delete;
B &operator=(B &&) = delete;
};
Where, A:
has user-defined constructors, such that a move constructor and move assigment operator will not be implicitly defined,
and where B, moreover:
declares and deletes a move constructor and a move assignment operator; as these are declared and defined as deleted, they will participate in overload resolution.
Before we continue through different standard versions, we define the following functions that we shall return to:
A getA() {
A a{};
return a;
}
B getB() {
B b{};
return b;
}
C++14
Now, in C++14 an implementation was allowed to implement copy(/move) elision for certain scenarios; citing [class.copy]/31 from N4140 (C++14 + editorial fixes) [emphasis mine]:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [...]
This elision of copy/move operations, called copy elision, is permitted in the following circumstances:
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
[...]
and, from [class.copy]/32 [emphasis mine]:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
But [class.temporary]/1 still placed the same semantic restrictions on an elided copy of an object as if the copy had actually not been elided [emphasis mine]
[..] Even when the creation of the temporary object is unevaluated (Clause [expr]) or otherwise avoided ([class.copy]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed.
Such that, even for a situation where copy elision was eligible (and performed), the conversion sequences from, say, a named object viable for NRVO, would need to go through overload resolution to find (possibly elided) converting constructors, and would start with a pass through overload resolution as if the object were designated by an rvalue. This means, that in C++14, the following was well-formed
auto aa{getA()}; // OK, and copy most likely elided.
whereas the following was ill-formed:
auto bb{getB()}; // error: use of deleted function 'B::B(B&&)'
as overload resolution would find the declared but deleted move constructor of B during the step of considering b in return b; in getB() as an rvalue. For A, no move constructor exists, meaning overload resolution for a in return a; in getA() with a as an rvalue would fail, and whereafter overload resolution without this kink would succeed in finding the copy constructor of A (which would subsequently be elided).
C++17
Now, in C++17 copy elision was made stronger by the concept of delayed (end entirely elided) materialization of temporaries, particularly adding [class.temporary]/3 [emphasis mine]:
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the non-deleted trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
This makes a large difference, as copy elision can now be performed for getB() without passing through the special rules of return value overload resolution (which previously picked the deleted move constructor), such that both of these are well-formed in C++17:
auto aa(getA()); // OK, copy elided.
auto bb(getB()); // OK, copy elided.
C++20
C++20 implements P1825R0 which allows even more implicit moves, expanding the cases where move construction or assignment may take place even when one would, at first glance, expect a copy construction/assignment (possible elided).
Summary
The quite complex rules with regard to move eagerness (over copying) can have some unexpected effects, and if a designer wants to make sure a type will not run into a corner case where a deleted move constructor or move assignment operator takes precedence in overload resolution over an non-deleted copy constructor or copy assignment operator, it is better to make sure that there are no move ctor/assignment operator available for overload resolution to find (for these cases), as compared to declaring them and defining them as explicitly-deleted. This argument does not apply for the move ctor/copy assignment operator however, as:
the standard contains no similar copy-eagerness (over move), and
there is no such thing as a class without a copy constructor or copy assignment operator, and removing these from overload resolution is basically(3) only possible in C++20 using requires-clauses.
As an example (and probably a GCC regression bug) of the difficulty of getting these rules right for a non-language lawyer, GCC trunk currently rejects the following program for C++20 (DEMO):
// B as above
B getB() {
B b{};
return b;
}
with the error message
error: use of deleted function 'B::B(B&&)'
In this case, one would expect a copy (possibly elided) to be chosen above in case B had deleted its move ctor. When in doubt, make sure the move ctor and assignment operator don't participate (i.e., exist) in overload resolution.
(3) One could declare a deleted assignment operator overloaded with both const- and ref-qualifiers, say const A& operator=(const A&) const && = delete;, which would very seldom be a viable candidate during overload solution (assignment to const rvalue), and which would guarantee the non-existence of the other non-const and &-qualified overloads that would otherwise likely to be valid overload candidates.

Why is it illegal to bind an r-value to a const l-value reference in special member functions?

For function parameters, it is possible to bind an r-value to an l-value const reference.
However, this does not seem to apply to special member function like the copy-constructor, and copy-assignment operator in C++11 and C++14. Is there a motivation for this?
When using C++17, it is possible to copy-construct, but not copy assign, from an r-value.
Is there a motivation why only the behavior for the copy-constructor was changed here?
All of this is demonstrated in the following example:
struct B {
B() = default;
B(B const&) = default;
B(B&&) = delete;
B& operator=(B const&) = default;
B& operator=(B&&) = delete;
};
void bar(B const &) {}
int main() {
bar(B{}); // does work
B(B{}); // only works in C++17
B b{};
b = B{}; // doesn't work
}
B(B{}); works since C++17 because of mandatory copy elision, the move-construction is omitted completely; the temporary object is initialized by the default constructor directly.
(emphasis mine)
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
...
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
Before C++17 this is an optimization and B(B{}); is ill-formed.
This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed
bar(B{}); works because there's only one overlaod of bar which taking lvalue-reference to const, to which rvalues could be bound; this doesn't change since C++11.
b = B{}; doesn't work because the overloaded move assignment operator is selected; even it's marked as delete explicitly it still participates in overload resolution[1]. For bar if you add an overloading taking rvalue-reference as
void bar(B&&)=delete;
It'll be selected and cause the program ill-formed too.
[1] Note that it's not true for deleted implicitly declared move constructors, which are ignored by overload resolution. (since C++14)
The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue).

When throwing an object, is it copied or moved?

As far as I know, thrown objects are copied by default. So the copy constructor should be called when I throw an object. I also know that the compiler may optimize and elide the copying. I have a simple program here where I'm throwing a class called X. It's expected that if I make the class non-copyable, the object can't be thrown. But something unexpected happens. If I delete the copy constructor, the compiler complains about that. If I comment the deletion of the copy constructor and delete the move constructor, the complier complains about deleting the move constructor.
the code:
class X
{
public:
int code;
X(int code) : code(code) {}
//X(X&) = delete;
//X(X&&) = delete;
};
void func()
{
X x(4);
throw x;
}
int main() { }
The error when I delete the copy constructor:
main.cpp: In function ‘void func()’:
main.cpp:13:11: error: use of deleted function ‘X::X(X&)’
throw x;
^
The error when I delete the move constructor:
main.cpp: In function ‘void func()’:
main.cpp:13:11: error: use of deleted function ‘X::X(X&&)’
throw x;
^
Can someone explain why this is happening?
Edit: If I delete the copy constructor and provide an implementation for the move constructor, the code works fine. Whereas if I delete the move constructor and provide an Implementation for the copy constructor, I still get the same error. Why can't I delete the move constructor?
code:
class X
{
public:
int code;
X(int code) : code(code) {}
X(X&) = default;
X(X&&) = delete;
};
void func()
{
X x(5);
throw x;
}
int main() { }
error:
main.cpp: In function ‘void func()’:
main.cpp:13:11: error: use of deleted function ‘X::X(X&&)’
throw x;
^
The initialization of the exception object follows the same rules as the initialization of other objects: when the initializer is an lvalue, the copy constructor is used, and when the initializer is an rvalue, the move constructor is used. However, see C++17 [class.copy.elision]/3:
... if the operand of a throw-expression (8.17) is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), overload resolution to select the constructor for the copy is first performed as if the object were designated
by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]
Here, you are throwing the object in a context where it is treated as an rvalue first. If overload resolution fails, it will then be treated as an lvalue. Therefore, in general, the compiler prefers to use the move constructor.
When the copy constructor is explicitly deleted, the compiler does not generate the move constructor ([class.copy.ctor]/8), so the code doesn't compile.
When the move constructor is explicitly deleted, the first stage of overload resolution selects the move constructor. An error occurs because the function selected by overload resolution is deleted. (Note: a defaulted move constructor that is defined as deleted is ignored by overload resolution ([class.copy.ctor]/10). An explicitly deleted move constructor is not ignored by overload resolution.)
When both the copy and move constructors are explicitly deleted, the previous paragraph still holds.
Quoting from cppreference
Even if copy initialization selects the move constructor, copy initialization from lvalue must be well-formed, and the destructor must be accessible (since C++14)
And
If the type of expression is a class type, its copy/move constructor and destructor must be accessible even if copy elision takes place.
Apparently, for well defined exception class a copy constructor is necessary, even if it wouldn't be used. C++11 standard didn't mandate that, but it was fixed in C++14.
I'm not 100% sure how to interpret "This may call the move constructor for rvalue expression" - whether move constructor is also required or not. For backwards compatibility it shouldn't be, but perhaps someone can quote standard on that.
When throwing an object, is it copied or moved?
The thrown object will be copy-initialised, and the lvalue local object will be treated as an rvalue - same as if returning a local lvalue.
Thus, if the type has a move constructor, then it will be moved in your example. If the class is only copyable but not movable, then it will be copied. Likewise, if the throw expression is not (treated as) an rvalue, then it is copied.
If the type is neither copyable nor movable, then it cannot be thrown. The copy or move may be elided in some cases but that has no effect on whether the type has to be copyable or movable.

Is copy/move elision allowed to make a program using deleted functions well-formed?

Consider the following code:
#include <iostream>
struct Thing
{
Thing(void) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
Thing(Thing const &) = delete;
Thing(Thing &&) = delete;
Thing & operator =(Thing const &) = delete;
Thing & operator =(Thing &&) = delete;
};
int main()
{
Thing thing{Thing{}};
}
I expect Thing thing{Thing{}}; statement to mean construction of temporary object of Thing class using default constructor and construction of thing object of Thing class using move constructor with just created temporary object as an argument. And I expect that this program to be considered ill-formed because it contains an invocation of deleted move constructor, even though it can be potentially elided. The class.copy.elision section of standard seems to demand this as well:
the selected constructor must be accessible even if the call is elided
The Wording for guaranteed copy elision through simplified value categories does not seem to allow it either.
However gcc 7.2 (and clang 4 as well, but not VS2017 which still does not support guaranteed copy elision) will compile this code just fine eliding move constructor call.
Which behavior would be correct in this case?
It doesn't make an ill-formed program build. It gets rid of the reference to the deleted function entirely. The appropriate wording in the proposal is here:
[dcl.init] bullet 17.6
If the initializer expression is a prvalue and the cv-unqualified
version of the source type is the same class as the class of the
destination, the initializer expression is used to initialize the
destination object. [ Example: T x = T(T(T())); calls the T default
constructor to initialize x. ]
The example further strengthens this. Since it indicates the whole expression must collapse into a single default construction.
The thing to note is that the deleted function is never odr-used when the copies are elided due to value categories, so the program is not referring to it.
This is an important distinction, since the other form of copy elision still odr-uses the copy c'tor, as described here:
[basic.def.odr]/3
... A constructor selected to copy or move an object of class type is
odr-used even if the call is actually elided by the implementation
([class.copy] ...
[class.copy] describes the other form of permissible (but not mandatory) copy-elision. Which, if we demonstrate with your class:
Thing foo() {
Thing t;
return t; // Can be elided according to [class.copy.elision] still odr-used
}
Should make the program ill-formed. And GCC complains about it as expected.
And by the way. If you think the previous example in the online compiler is a magicians trick, and GCC complains because it needs to call the move c'tor. Have a look at what happens when we supply a definition.

Move constructor is required even if it is not used. Why?

Why?! Why C++ requires the class to be movable even if it's not used!
For example:
#include <iostream>
using namespace std;
struct A {
const int idx;
// It could not be compileld if I comment out the next line and uncomment
// the line after the next but the moving constructor is NOT called anyway!
A(A&& a) : idx(a.idx) { cout<<"Moving constructor with idx="<<idx<<endl; }
// A(A&& a) = delete;
A(const int i) : idx(i) { cout<<"Constructor with idx="<<i<<endl; }
~A() { cout<<"Destructor with idx="<<idx<<endl; }
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
The output is (the move constructor is not called!):
Constructor with idx=0
Constructor with idx=1
Destructor with idx=1
Destructor with idx=0
The code can not be compiled if moving constructor is deleted ('use of deleted function ‘A::A(A&&)’'. But if the constructor is not deleted it is not used!
What a stupid restriction?
Note:
Why do I need it for? The practical meaning appears when I am trying to initialize an array of objects contains unique_ptr field.
For example:
// The array of this class can not be initialized!
class B {
unique_ptr<int> ref;
public:
B(int* ptr) : ref(ptr)
{ }
}
// The next class can not be even compiled!
class C {
B arrayOfB[2] = { NULL, NULL };
}
And it gets even worse if you are trying to use a vector of unique_ptr's.
Okay. Thanks a lot to everybody. There is big confusion with all those copying/moving constructors and array initialization. Actually the question was about the situation when the compiler requires a copying consrtuctor, may use a moving construcor and uses none of them.
So I'm going to create a new question a little bit later when I get a normal keyboard. I'll provide the link here.
P.S. I've created more specific and clear question - welcome to discuss it!
A a[2] = { 0, 1 };
Conceptually, this creates two temporary A objects, A(0) and A(1), and moves or copies them to initialise the array a; so a move or copy constructor is required.
As an optimisation, the move or copy is allowed to be elided, which is why your program doesn't appear to use the move constructor. But there must still be a suitable constructor, even if its use is elided.
A a[2] = { 0, 1 };
This is aggregate initialization. §8.5.1 [dcl.init.aggr]/p2 of the standard provides that
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
§8.5 [dcl.init]/p16, in turn, describes the semantics of copy initialization of class type objects:
[...]
If the destination type is a (possibly cv-qualified) class type:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated (13.3.1.3), and the best one is chosen through overload
resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its
argument(s). If no constructor applies, or the overload resolution is
ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
Since 0 and 1 are ints, not As, the copy initialization here falls under the second clause. The compiler find a user-defined conversion from int to A in your A::A(int) constructor, calls it to construct a temporary of type A, then performs direct initialization using that temporary. This, in turn, means the compiler is required to perform overload resolution for a constructor taking a temporary of type A, which selects your deleted move constructor, which renders the program ill-formed.