19

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?

xskxzr
  • 12,442
  • 12
  • 37
  • 77
xmh0511
  • 7,010
  • 1
  • 9
  • 36
  • 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 Jan 20 '20 at 09:28
  • You create const ref to an existing object, no copy, nop move, no construct at all... – Klaus Jan 20 '20 at 09:30
  • 5
    Notice that `#1` is valid since C++17 – Jarod42 Jan 20 '20 at 09:32
  • 2
    @Klaus,the source type is not compatible with the target type – xmh0511 Jan 20 '20 at 09:34
  • 2
    @LukaRahne from int to Data ,need user-defined conversion – xmh0511 Jan 20 '20 at 09:36
  • @Jarod42 talk about the question in c++11,`#1` is invalid,but `#2` is valid ,why? – xmh0511 Jan 20 '20 at 09:39
  • You have deleted the copy constructor, So, #1 will be error statement. In #2 here its just an alias to right hand side "a", and no object creation. – Build Succeeded Jan 20 '20 at 10:09
  • 1
    @Mannoj Note ,the source type is not compatiabe with destination type – xmh0511 Jan 20 '20 at 12:09
  • https://stackoverflow.com/questions/39548639/assign-one-class-object-to-another-class-object-in-c But I think, you have deleted the copy constructor as it first try to convert the int to class type by finding the appropriate conversion, which expected in form of converting constructor . – Build Succeeded Jan 20 '20 at 12:25
  • @Mannoj `#1` is used to verificate the copy-initialized which need copy-constructor,and consider the `#2`,the reference is bound to the temporary object which is copy-initialized from `a`,but `#2` is complied with no fault – xmh0511 Jan 20 '20 at 12:38
  • N3337 is very old, it would be better to stick to C++17 or later documents. Or perhaps if you intend to ask about C++11 specifically (excluding C++17) then add C++11 as tag. – M.M Jan 24 '20 at 03:27
  • @M.M yes ,I have added the c++11 tag – xmh0511 Jan 24 '20 at 05:13

6 Answers6

5

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.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • yes,c++17 guaranteed copy elision,but at the same complier,`Data d = 0` is still ill-formed ,why this case is not the same behavior with the copy-initialization of initializing reference – xmh0511 Feb 04 '20 at 14:46
  • @jackX I don't know, maybe we need to check the logic of the compiler to see the reason. – xskxzr Feb 05 '20 at 14:28
4

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • @NicoBolas the key point is from `int` to `Data`,the implicitly conversion is `Data::Data(int)` or anything others,if the conversion would be `Data::Data(int)`,even though there are no `copy/move contructor`,it doesn't matter,But as the above the standard I have cited,`T const& rf = object` ,if the object is no T or derived from T,the conversion form object to T need to use `copy-initialization` to generate a temporary value of type T,and then the reference is bound to the temporary value,If I misread the standard ,please correct me with related standard,thanks – xmh0511 Jan 23 '20 at 05:20
  • my understand for a temporary of type T is constructed and copy-initialized from object is `T temporary = object`,as you said "copy-initialization" means 'using an = sign', Do you agree with me?Now,we substitute the arguments ,the result is Data temporary = a,then the reference is bound to `temporary`,So for `Data temporary = a`,even though exists copy-elision,the copy/move constructor must be checked – xmh0511 Jan 23 '20 at 05:25
  • @jackX: "*the conversion form object to T need to use copy-initialization to generate a temporary value of type T*" A copy constructor is called when you attempt to construct an object of type `T` with some lvalue of type `T`. We don't have a `Data` yet; all we have is an `int`. So we copy-initialize the `Data` temporary from the `int`, pursuant to the rules I outlined. At no point is there an attempt to call a copy constructor. – Nicol Bolas Jan 23 '20 at 05:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206493/discussion-between-nicol-bolas-and-jack-x). – Nicol Bolas Jan 23 '20 at 06:57
  • 1
    There are two temporaries involved. A temporary `t1` of type `T` is constructed and the reference is bound to `t1`. To copy-initialize `t1` from `a`, another temporary `t2` should be constructed according to the rules, and `t1` is then direct-initialize from `t2`. The problem comes from the process that direct-initializes `t1` from `t2`. – xskxzr Feb 07 '20 at 08:31
2

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.

Red.Wave
  • 2,790
  • 11
  • 17
  • 1
    the key point is in the copy-initialization(i.e. A temporary 'Data' is constructed from an 'int') – xmh0511 Feb 05 '20 at 16:18
  • I just fail to see the problem. Is it due to mistaking reference with object or what? Even with c99 and private copy ctor(=delete syntax did not exist), code must compile. If it did not compile, I would ask for information on code-breaking new rules. – Red.Wave Feb 06 '20 at 07:14
  • 1
    copy-initialization need to check whether the copy /move constructor is availiable in C++11 – xmh0511 Feb 06 '20 at 12:25
  • Big NO. Obviously mistaking object for reference. Remove the ampersand in that line and we can discuss the situation. – Red.Wave Feb 06 '20 at 17:25
  • I found reference quotes from standard, which though lacks proper compilation of reasons (they are spread across large potion of document) as of why elision doesn't happen in C++14 case, In C++11 it wouldn't happen. – Swift - Friday Pie Feb 07 '20 at 07:40
  • How do you interpret that `Data d = 0;` is ill-formed? – xskxzr Feb 07 '20 at 08:27
  • It is not ill-formed. It must compile. A simple converting constructor invokation. – Red.Wave Feb 07 '20 at 11:36
0

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.

ivan.ukr
  • 2,853
  • 1
  • 23
  • 41
0

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.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
-1

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.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • 1
    [here](http://www.enseignement.polytechnique.fr/informatique/INF478/docs/Cpp/en/cpp/language/reference_initialization.html) is the standard about the copy initialization of reference .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).It means the temporary object which is bound by the reference is initialized as the same form as `#1` – xmh0511 Jan 20 '20 at 11:47
  • Cited document are not standard (seems to be local copy of cppreference). Answer from anacongua is telling you the same. – Jean-Baptiste Yunès Jan 20 '20 at 16:33
  • if cppreference is not standard ,[this](http://eel.is/c++draft/dcl.init.ref#5.4.1) must be,I copy the section here – xmh0511 Jan 21 '20 at 01:36
  • 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 – xmh0511 Jan 21 '20 at 01:37
  • Note the bold part,It means the result which is bound by reference is constructed by using the **copy-initialization** – xmh0511 Jan 21 '20 at 01:39