Consider the following two classes:
class B
{
public:
B() { }
B(const B& b) = delete; //Move ctor not implicitly declared
};
class A
{
public:
A() { }
operator B()
{
return B();
}
};
I can see why this code compiles fine:
A a;
B b = a;
Following the rules of copy-initialization, the object "a" gets converted to a prvalue of type B and since in C++17 the copy constructor is not needed anymore there's no error:
If T is a class type, and the cv-unqualified version of the type of
other is not T or derived from T, 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 (until
C++17)prvalue expression (since C++17) 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. (until C++17)
However why does this direct list-initialization compile too?
A a;
B b{ a };
I couldn't find any wording in the list-initialization stating the compiler should attempt to convert A into B in this case. Only that overload resolution on constructors is considered:
If the previous stage does not produce a match, all constructors of T
participate in overload resolution against the set of arguments that
consists of the elements of the braced-init-list, with the restriction
that only non-narrowing conversions are allowed
However in this case the copy constructor is deleted, so shouldn't it not be selected by overload resolution?
This is CWG 2327. You're correct as far as the standard goes, but some compilers additionally consider conversion functions in this context as well - because it really makes sense to.
Related
I was trying to overload the casting operator in C++ for practice, but I encountered a problem and I can't figure out the issue. In the example, you can implicitly cast fine, but it causes an error when you try to explicitly cast.
struct B
{
B() = default;
B( B& rhs ) = default;
};
struct A
{
operator B()
{
return B();
}
};
int main()
{
A a;
B example = a; //fine
B example2 = static_cast<B>(a); //error
}
The error is:
error C2440: 'static_cast': cannot convert from 'A' to 'B'
message : No constructor could take the source type, or constructor overload resolution was ambiguous
The problem only appears if you define the copy constructor in the B structure. The problem goes away, though, if you define the move constructor, too, or make the copy constructor take in a const B& ( B( const B& rhs ) ).
I think the problem is that the explicit cast is ambiguous, but I don't see how.
I was looking at a similar problem, here, but in that case I could easily see how the multiple options for casting led to ambiguity while I can't here.
static_cast<B>(a);
This expression is an rvalue, more loosely described as a temporary value.
The B class has no suitable constructor. The B( B &rhs) constructor is not suitable, mutable lvalue references don't bind to temporaries, hence the compilation failure.
Before C++17:
Both lines do effectively the same thing. B example = /*...*/; is copy-initialization which will first convert the right-hand side to the type B if necessary in some suitable manner, resulting in a temporary object from which example is then initialized by choosing a suitable constructor (typically a copy or move constructor).
Because A is not related to B via inheritance, there is no way to bind a directly to the rhs parameter in the (copy) constructor of B. There must first be a conversion from a to a B which is possible implicitly or explicitly via the conversion function you defined.
The result of the conversion will always be a prvalue. A prvalue however can not be bound to a non-const lvalue reference. Therefore it doesn't help that the conversion is possible, the B( B& rhs ) constructor overload is still not viable.
A constructor B( const B& rhs ) (which is also the signature of the implicitly-declared copy constructor if you don't declare one yourself) would however be viable because the prvalue resulting from the conversion can be bound to a const lvalue reference.
Therefore both lines are ill-formed, before C++17.
Since C++17 the mandatory copy elision rules state that initialization of a variable of a type B from a prvalue of type B is done exactly as if the variable was directly initialized by the initializer of the prvalue, so that no overload resolution on the constructor will be performed at all. Whether there is a copy constructor or how exactly it is declared is then irrelevant. Similarly the rules for copy-initialization from a non-reference compatible lvalue such as a have been updated to have the same effect.
So since C++17 both lines will compile.
If you are using MSVC in a default configuration with the language standard set below C++17, the first line will compile, but that is not standard-conforming. By default when set to language versions below C++20 MSVC uses a slight dialect of C++ which is not conforming to the standard in some areas. You can set the standards conformance mode (/permissive- flag) to set MSVC to behave (more) standard-conforming.
I am having trouble understanding why the following copy-initialization doesn't compile:
#include <memory>
struct base{};
struct derived : base{};
struct test
{
test(std::unique_ptr<base>){}
};
int main()
{
auto pd = std::make_unique<derived>();
//test t(std::move(pd)); // this works;
test t = std::move(pd); // this doesn't
}
A unique_ptr<derived> can be moved into a unique_ptr<base>, so why does the second statement work but the last does not? Are non-explicit constructors not considered when performing a copy-initialization?
The error from gcc-8.2.0 is:
conversion from 'std::remove_reference<std::unique_ptr<derived, std::default_delete<derived> >&>::type'
{aka 'std::unique_ptr<derived, std::default_delete<derived> >'} to non-scalar type 'test' requested
and from clang-7.0.0 is
candidate constructor not viable: no known conversion from 'unique_ptr<derived, default_delete<derived>>'
to 'unique_ptr<base, default_delete<base>>' for 1st argument
Live code is available here.
A std::unique_ptr<base> is not the same type as a std::unique_ptr<derived>. When you do
test t(std::move(pd));
You call std::unique_ptr<base>'s conversion constructor to convert pd into a std::unique_ptr<base>. This is fine as you are allowed a single user defined conversion.
In
test t = std::move(pd);
You are doing copy initialization so so you need to convert pd into a test. That requires 2 user defined conversions though and you can't do that. You first have to convert pd to a std::unique_ptr<base> and then you need to convert it to a test. It's not very intuitive but when you have
type name = something;
whatever something is needs to be only a single user defined conversion from the source type. In your case that means you need
test t = test{std::move(pd)};
which only uses a single implicit user defined like the first case does.
Lets remove the std::unique_ptr and look at in a general case. Since std::unique_ptr<base> is not the same type as a std::unique_ptr<derived> we essentially have
struct bar {};
struct foo
{
foo(bar) {}
};
struct test
{
test(foo){}
};
int main()
{
test t = bar{};
}
and we get the same error because we need to go from bar -> foo -> test and that has one user defined conversion too many.
The semantics of initializers are described in [dcl.init] ¶17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, 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 ([over.match.ctor]), and the best one is chosen through
overload resolution. 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
[over.match.copy], and the best one is chosen through overload
resolution. 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 is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived> to a unique_ptr<base> as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived> to a unique_ptr<base> to a test. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&& to std::unique_ptr<base>&& is required, in the second case the base pointer would also need to be converted to test (for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd) and then move assigning it would work as well.
Take the following (contrived) class hierarchy that prints to the console from the constructors and destructors:
#include <iostream>
class A {
public:
A() { std::cout << "A"; }
~A() { std::cout << "~A"; }
};
class B : public A {
public:
B() { std::cout << "B"; }
~B() { std::cout << "~B"; }
};
void func(A a) { }
int main() {
B b;
func(b);
std::cout << "X";
return 0;
}
Compiled under linux with gcc, it prints AB~AX~B~A as expected (the ~A printed before X is a result of the pass-by-value to func which creates a copy that is destructed when the function returns).
But compiled under windows with VS2008 it prints AB~A~AX~B~A - where does the extra ~A come from? It vanishes if the copy xtor is explicitly defined A(A& that) {}; or if the destructor is declared virtual (as it should be, arguably).
The comments suggest that MSVC 2008 uses a temporary that g++ doesn't, to pass the parameter. If so, this is a bug. From C++03 [dcl.init]/12:
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;
Now here is the crucial bit. In T x = a;, if a is not a T or derived from T, then that is equivalent to T x = T(a);, and an extra temporary is conceptually used. (This temporary is eligible for copy-elision).
However, if a is a T or derived from T, then there must not be an extra temporary.It is the same as T x(a);.
In this question's code, since B derives from A, there must not be a temporary.
The supporting text in C++03 is under [dcl.init]/14 (I have highlighted the parts relevant to this question's code sample):
If the destination type is a (possibly cv-qualified) class type:
If the class is an aggregate (8.5.1), and the initializer is a brace-enclosed list, see 8.5.1.
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(s) 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 destination type. 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 imple-
mentation 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.
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.
Except for supporting multiple arguments, disallowing narrowing conversion, matching constructor taking std::initializer_list argument, what else is different for copy-list-initialization against traditional copy-initialization?
To be specific, assume there are two user-defined types, A and B:
class A {...};
class B {...};
B b;
A a1 = {b};
A a2 = b;
What kind of definition of A and B will make a difference on those two forms of initialization? e.g. Is there a certain definition of A and B that will make one of the initialization legal but the other illegal, or both legal but with different semantics, or both illegal with different causes?
(Assume A doesn't have a constructor taking std::initializer_list argument.)
EDIT: Adding a link to a somewhat related question of mine: What is the supposed behavior of copy-list-initialization in the case of an initializer with a conversion operator?
Copy-initialization always considers availability of copy constructors, while copy-list-initialization doesn't.
class B {};
struct A
{
A(B const&) {}
A(A const&) = delete;
};
B b;
A a1 = {b}; // this compiles
A a2 = b; // this doesn't because of deleted copy-ctor
This is because copy-list-initialization is identical to direct-list-initialization except in one situation - had A(B const&) been explicit, the former would've failed, while the latter will work.
class B {};
struct A
{
explicit A(B const&) {}
};
int main()
{
B b;
A a1{b}; // compiles
A a2 = {b}; // doesn't compile because ctor is explicit
}
Probably, the behaviour of the new copy-list-initialization was defined to be "good" and consistent, but the "weird" behaviour of old copy-initialization couldn't be changed because of backward compatibility.
As you can see the rules for list-initialization in this clause are identical for direct and copy forms.
The difference related to explicit is described only in the chapter on overload resolution. But for traditional initialization direct and copy forms are not identical.
The traditional and brace initializations are defined separately, so there's always a potential for some (probably unintended) subtle differences.
The differences I can see from the excerpts of the standard:
1. Already mentioned differences
narrowing conversions are disallowed
multiple arguments are possible
braced syntax prefers initializer-list constructors if they present:
struct A
{
A(int i_) : i (i_) {}
A(std::initializer_list<int> il) : i (*il.begin() + 1) {}
int i;
}
A a1 = 5; // a1.i == 5
A a2 = {5}; // a2.i = 6
2. Different behaviour for aggregates
For aggregates you can't use braced copy-constructor, but can use traditional one.
struct Aggr
{
int i;
};
Aggr aggr;
Aggr aggr1 = aggr; // OK
Aggr aggr2 = {aggr}; // ill-formed
3. Different behaviour for reference initialization in presence of conversion operator
Brace initialization can't use operators of conversion to reference type
struct S
{
operator int&() { return some_global_int;}
};
int& iref1 = s; // OK
int& iref2 = {s}; // ill-formed
4. Some subtle differences in initialization of object of class type by object of other type
These difference are marked by [*] in the excerpts of the Standard at the end of this answer.
Old initialization uses notion of user-defined conversion sequences (and, particularly, requires availability of copy constructor, as was mentioned)
Brace initialization just performs overload resolution among applicable constructors, i.e. brace initialization can't use operators of conversion to class type
These differences are responsible for some not very obvious (for me) cases like
struct Intermediate {};
struct S
{
operator Intermediate() { return {}; }
operator int() { return 10; }
};
struct S1
{
S1(Intermediate) {}
};
S s;
Intermediate im1 = s; // OK
Intermediate im2 = {s}; // ill-formed
S1 s11 = s; // ill-formed
S1 s12 = {s}; // OK
// note: but brace initialization can use operator of conversion to int
int i1 = s; // OK
int i2 = {s}; // OK
5. Difference in overload resolution
Different treatment of explicit constructors
See 13.3.1.7 Initialization by list-initialization
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations (13.3.1.3, 13.3.1.4), where only converting constructors
are considered for copy initialization. This restriction only applies
if this initialization is part of the final result of overload
resolution. — end note ]
If you can see more differences or somehow correct my answer (including grammar mistakes), please do.
Here are the relevant (but long) excerpts from the current draft of the C++ standard (I haven't found a way to hide them under spoiler):
All of them are located in the chapter 8.5 Initializers
8.5 Initializers
If the initializer is a (non-parenthesized) braced-init-list, the
object or reference is list-initialized (8.5.4).
If the destination type is a reference type, see 8.5.3.
If the destination type is an array of characters, an array of char16_t, an
array of char32_t, or an array of wchar_t, and the initializer is a
string literal, see 8.5.2.
If the initializer is (), the object is
value-initialized.
Otherwise, if the destination type is an array,
the program is ill-formed.
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.
Otherwise, if the source type is a
(possibly cv-qualified) class type, conversion functions are
considered. The applicable conversion functions are enumerated
(13.3.1.5), and the best one is chosen through overload resolution
(13.3). The user-defined conversion so selected is called to convert
the initializer expression into the object being initialized. If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed.
Otherwise, the initial value of the object being
initialized is the (possibly converted) value of the initializer
expression. Standard conversions (Clause 4) will be used, if
necessary, to convert the initializer expression to the cv-unqualified
version of the destination type; no user-defined conversions are
considered. If the conversion cannot be done, the initialization is
ill-formed.
8.5.3 References ...
8.5.4 List-initialization
List-initialization of an object or reference of type T is defined as
follows:
If T is an aggregate, aggregate initialization is
performed (8.5.1).
Otherwise, if the initializer list has no
elements and T is a class type with a default constructor, the object
is value-initialized.
Otherwise, if T is a specialization of
std::initializer_list<E>, a prvalue initializer_list object is
constructed as described below and used to initialize the object
according to the rules for initialization of an object from a class of
the same type (8.5).
[*] Otherwise, if T is a class type,
constructors are considered. The applicable constructors are
enumerated and the best one is chosen through overload resolution
(13.3, 13.3.1.7). If a narrowing conversion (see below) is required to
convert any of the arguments, the program is ill-formed.
Otherwise, if the initializer list has a single element of type E and
either T is not a reference type or its referenced type is
reference-related to E, the object or reference is initialized from
that element; if a narrowing conversion (see below) is required to
convert the element to T, the program is ill-formed.
Otherwise, if
T is a reference type, a prvalue temporary of the type referenced by T
is copy-list-initialized or direct-list-initialized, depending on the
kind of initialization for the reference, and the reference is bound
to that temporary. [ Note: As usual, the binding will fail and the
program is ill-formed if the reference type is an lvalue reference to
a non-const type. — end note ]
Otherwise, if the initializer list
has no elements, the object is value-initialized.
Otherwise, the program is ill-formed.