Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy initialization with deleted copy constructor in reference initialization

Consider the follow code:

#include <iostream>
class Data{
public:
    Data() = default;
    Data(Data const&) = delete;
    Data(int) {

    }
};
int main(){
  int a = 0;
  const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array
  Data const& d_rf = a;          // #2 but here can be complied
  // accroding to the standard, the reference in #2 is bound to a temporary object, the temporary is copy-initialized from the expression
}

[dcl.init.ref]

If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion ([dcl.init], [over.match.copy], [over.match.conv]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered

Copy initialization

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions 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 ([over.match]). 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.

Accroding to the standard, the type of a is int, and the type of the initialized reference is Data, so from int to Data, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion. It means Data const& d_rf = a; can be translated to Data temporary = a; Data const& d_rf = temporary;. For Data temporary = a;, even though copy elision exists , the copy/move constructor must be checked whether it is available, but the copy constructor of class Data has been deleted, why can it be complied?

Here are some quote of standard
Copy initialization of reference from enseignement

Copy initialization of reference from cppreference

If the reference is an lvalue reference:

If object is an lvalue expression, and its type is T or derived from T, and is equally or less cv-qualified, then the reference is bound to the object identified by the lvalue or to its base class subobject.
If object is an lvalue expression, and its type is implicitly convertible to a type that is either T or derived from T, equally or less cv-qualified, then the non-explicit conversion functions of the source type and its base classes that return lvalue references are considered and the best one is selected by overload resolution. The reference is then bound to the object identified by the lvalue returned by the conversion function (or to its base class subobject)

Otherwise, if the reference is either rvalue reference or lvalue reference to const:

If object is an xvalue, a class prvalue, an array prvalue, or a function lvalue type that is either T or derived from T, equally or less cv-qualified, then the reference is bound to the value of the initializer expression or to its base subobject.
If object is a class type expression that can be implicitly converted to an xvalue, a class prvalue, or a function value of type that is either T or derived from T, equally or less cv-qualified, then the reference is bound to the result of the conversion or to its base subobject.
Otherwise, a temporary of type T is constructed and copy-initialized from object. The reference is then bound to this temporary. Copy-initialization rules apply (explicit constructors are not considered).
[example:
const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array ]

UPDATE:

We consider the code under N337

according to the standard, the value a's type is int, and the destination type that the reference refer to is Data, so the complier needs to generate a temporary of type Data by copy initialization. There is no doubt here,so we focus on copy initialization. The source type is int and the destination type is Data, this situation conforms to :

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;

NOTE the bold part, it does not mean the value int directly initializes the temporary by Data::Data(int). It means, int is firstly converted to Data by Data::Data(int), then this result directly initializes the temporary which is the object that is the destination of the copy-initialization here. If we use code to express the bold part, it is just like Data temporary(Data(a)).

The above rules is here:

— 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.

Please reback to Data temporary(Data(a)). Obviously, the copy/move constructor is the best match for argument Data(a). However, Data(Data const&) = delete;, so the copy/move constructor is not available. Why does the complier not report the error?

like image 308
xmh0511 Avatar asked Jan 20 '20 09:01

xmh0511


People also ask

Does copy initialization call copy constructor?

In other words, a good compiler will not create a copy for copy-initialization when it can be avoided; instead it will just call the constructor directly -- ie, just like for direct-initialization.

Does passing by reference call copy constructor?

When an object (or built-in type) is passed by reference to a function, the underlying object is not copied. The function is given the memory address of the object itself. This saves both memory and CPU cycles as no new memory is allocated and no (expensive) copy constructors are being called.

Why reference is used in copy constructor?

A copy constructor defines what copying means,So if we pass an object only (we will be passing the copy of that object) but to create the copy we will need a copy constructor, Hence it leads to infinite recursion. So, A copy constructor must have a reference as an argument.

Why do we delete the copy constructor?

When to delete copy constructor and assignment operator? Copy constructor (and assignment) should be defined when ever the implicitly generated one violates any class invariant. It should be defined as deleted when it cannot be written in a way that wouldn't have undesirable or surprising behaviour.

What is copy initialization for classes?

Consider the following line of code: This statement uses copy initialization to initialize newly created integer variable x to the value of 5. However, classes are a little more complicated, since they use constructors for initialization. This lesson will examine topics related to copy initialization for classes. Given our Fraction class:

What is the difference between copy initialization and direct initialization?

In addition, the implicit conversion in copy-initialization must produce T directly from the initializer, while, e.g. direct-initialization expects an implicit conversion from the initializer to an argument of T 's constructor.

What is copy constructor in C++?

A copy constructor is a member function which initializes an object using another object of the same class. A copy constructor has the following general function prototype: Following is a simple example of copy constructor. When is copy constructor called? 1. When an object of the class is returned by value. 2.

What is initializer in C++?

Initializes an object from another object. 1) when a named variable (automatic, static, or thread-local) of a non-reference type T is declared with the initializer consisting of an equals sign followed by an expression.


2 Answers

This issue is addressed by Issue 1604, and the proposed solution seems to confirm that such code should be ill-formed, so I would consider it as a compiler bug.

Fortunately, since C++17, this code becomes well-formed because of guaranteed copy elision, which agrees with the compilers.

like image 186
xskxzr Avatar answered Sep 20 '22 22:09

xskxzr


Let's examine what the standard says:

Otherwise, a temporary of type T is constructed and copy-initialized from object. The reference is then bound to this temporary. Copy-initialization rules apply (explicit constructors are not considered).

So, a temporary of type T is constructed. This temporary is copy-initialized from the given object. OK... how does that work?

Well, you cited the rule explaining how copy-initialization from the given value will work. It will attempt to invoke user-defined conversions, by sifting through the applicable constructors of T and the conversion operators on the value (and there aren't any, since it is of type int). There is an implicit conversion constructor on T which takes an object of type int. So that constructor is called to initialize the object.

The reference is then bound to that temporary, per the rules you cited.

At no time is there any attempt to call any of the deleted functions. Just because it's called "copy-initialization" does not mean that a copy constructor will be called. It's called "copy-initialization" because it is (usually) provoked using an = sign, and therefore it looks like "copying".

The reason Data d = a; doesn't work is because C++11 defines this operation to first convert a into a Data temporary, then to initialize d with that temporary. That is, it's essentially equivalent to Data d = Data(a);. The latter initialization will (hypothetically) invoke a copy constructor, thus leading to the error.

like image 20
Nicol Bolas Avatar answered Sep 22 '22 22:09

Nicol Bolas