19
votes

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?

6
Why you think that copy-initialization is done for line #2. I think that at best operator& is called, but is not defined in your case.Luka Rahne
Notice that #1 is valid since C++17Jarod42
@Klaus,the source type is not compatible with the target typexmh0511
@LukaRahne from int to Data ,need user-defined conversionxmh0511
@Mannoj Note ,the source type is not compatiabe with destination typexmh0511

6 Answers

4
votes

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.

4
votes

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.

2
votes

The accepted answer looks irrelevant; The sequence is as simple as it looks. No copy/move construuctor or optimizations are involved; all of theme are strictly irrelevant. A temporary 'Data' is constructed from an 'int', using a conversion ctor. The prvalue is then bound to a 'const' lvalue reference. That is all. If this does not look right, then we are discussing different programming languages; I am certainly talking about C++.

PS: I cannot cite references to standard, cause I cannot afford to get it.

EDIT================================

'=' is just another way to call a single argument ctor not marked as 'explicit'. It is the same as curly or round braces -as long as the ctor accepts single parameters, unless the ctor is 'explicit'. Nobody learns programming by reading standards; It is for compiler designers.

Best, FM.

0
votes

I tend to agree with @Red.Wave - the temporary object is constructed using Data::Data(int), and then reference "d_rf" is initialized with its address. Copy constructor is not involved here at all.

0
votes

Consider this code:

class Data{
public:
    Data() = default;
    Data(Data const&) = delete;
    Data(int) {
    }
};

class Data2{
public:
    Data2() = default;
    Data2(Data &) = delete;
    Data2(int) {
    }
};

int main()
{
    Data a {5};
    Data b  = 5;

    Data2 a2{5};
    Data2 b2  = 5;
}

Until C++17 standard only initialization of b is ill-formed. The two forms of initialization used here are described as follows (copy from N4296):

15 The initialization that occurs in the = form of a brace-or-equal-initializer or condition (6.4), 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 ]

16 The initialization that occurs in the forms

T x(a); 
T x{a}; 

as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), mem-initializers (12.6.2), and the braced-init-list form of a condition is called direct-initialization.

Then

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.

It's not our case, continue to the next paragraph

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

Thus, provided that constant 5 is not of type Data or Data2, then for b and b2 the copy-constructor was called during copy-initialization after conversion of 5 to proper type via direct initialization of temporal object, which can be bound to const Data& argument of constructor but not to Data& when constructor candidates are considered.

b has had its copy-constructor deleted, so initialization is ill-formed. b2 had been prohibited only to be initialized from non-const object which call cannot be bound to this case. Copy elision didn't happen per C++11/14 rules.

-1
votes

When compiling your code in C++11 (you used =default and =delete thus it is at least C++11) the error is at line #1, the other (#2) has no problem:

$ g++ -Wall --std=c++11 -o toto toto.cpp
toto.cpp:14:8: error: copying variable of type 'Data' invokes deleted constructor
  Data d = a;  //#1
       ^   ~
toto.cpp:5:5: note: 'Data' has been explicitly marked deleted here
    Data(Data const&) = delete;
    ^
1 error generated.

For #1, first an [over.match.copy] is made with the help of a [class.conv.ctor]. Thus it is converted to Data d = Data(a). Second as you are in the scope of move semantic compiler is not able to find the right ctor because:

11.4.4.2 Copy/Move constructors

  1. [ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]

alas the copy-ctor has been deleted.