Why is a copy constructor needed here?

Consider the following code:

struct S { S() {} void f(); private: S(const S&); }; int main() { bool some_condition; S my_other_S; (some_condition ? S() : my_other_S).f(); return 0; } 

gcc cannot compile this by saying:

 test.cpp: In function 'int main()': test.cpp:6:5: error: 'S::S(const S&)' is private test.cpp:13:29: error: within this context 

I do not understand why the construction of a copy should be performed on this line. The goal is to simply call f() on the default instance of S or in my_other_S , i.e. it should be equivalent in:

 if (some_condition) S().f(); else my_other_S.f(); 

What is the difference between the first case and why is a copy constructor required?

EDIT : Is there a way to express “performing this operation or temporary in the previous object” in the context of the expression?

+4
source share
4 answers

The result ?: Is an rvalue, a new object if one of the arguments is an rvalue. To create this rvalue, the compiler must copy any result.

 if (some_condition) S().f(); // Compiler knows that it rvalue else my_other_S.f(); // Compiler knows that it lvalue 

This is for the same reason that you cannot do

 struct B { private: B(const B&); }; struct C { C(B&); C(const B&); }; int main() { B b; C c(some_condition ? b : B()); } 

I changed my example because the old one was a little suck. You can clearly see here that it is not possible to compile this expression because the compiler cannot know which constructor to call. Of course, in this case, the compiler can force both arguments to const B& , but for some reason this is not very relevant, it will not.

Edit: No, no, because there is no way to compile this expression, since important data about it (rvalue or lvalue) changes at run time. The compiler is trying to fix this problem for you by converting it to rvalue by creating a copy, but it cannot, because it cannot copy, so it cannot compile.

+8
source

From [expr.cond] (wording on project n3242):

Otherwise, if the second and third operands have different types and either have (possibly cv-qualified) the class type, or both glvalues ​​of the same category of values ​​and the same type, except for cv-qualification, an attempt is made to converting each of these operands to the type of the other. The process of determining whether it is possible to transform the expression of the operand E1 type T1 to the expression of the operand E2 type T2 as follows:

  • If E2 is an lvalue: E1 can be converted to E2 , if E1 can be implicitly converted (section 4) to the "lvalue reference to T2 " type, provided that the link must be directly linked (8.5.3) to the lvalue in the conversion .
  • If E2 - the value x: E1 can be converted to conform to E2 , if E1 can be implicitly converted to the type "rvalue reference to T2 ", subject to the constraint that the link must directly bind.
  • If E2 is an rvalue or if none of the above transforms can be performed, and at least one of the operands has (possibly cv-qualified) class type:

    • If E1 and E2 are of class type and the base types of classes are the same or one is the base class of the other: E1 can be converted to E2 if the class T2 is the same type as the base class of the class T1 , and the cv-qualification T2 is such the same cv qualification, or a larger cv qualification than the cv qualification T1 . If a conversion is applied, E1 changed to a prvalue of type T2 by copy-initializing the temporary type T2 from E1 and using that temporary as the converted operand.

Copy-initialization is mentioned in this rule, but it does not apply, since both operands are of the same type

If the second and third operands are glvalues ​​of the same category of values ​​and are of the same type, the result is of the type and category of values, and this is a bit field if the second or third operand is a bit field, or if both are bit fields.

This rule does not apply because S() is an rvalue, and my_other_S is an lvalue.

Otherwise, the result will be prvalue. If the second and third operands do not have the same type and have (possibly cv-qualit) the class type, then to determine the conversions (if any) are applied to the operands (13.3.1.2, 13.6), If the overload resolution fails , the program is poorly formed. Otherwise, the transformations specified in this way are applied, and the converted operands are used instead of the original operands for the rest of this section. The standard Lvalue-to-rvalue conversions (4.1), the standard conversion between lines (4.2) and the to-pointer function (4.3) are formed in the second and third operands. After these transformations, one of the following actions will be performed:

  • The second and third operands are of the same type; result of this type. If the operands are of class type, the result is a temporary prvalue for the type of result that is initialized with a copy from the second operand or third operand, depending on the value of the first operand. li>

This rule applies, the result is initialized by copying (my selection).

+7
source

This is an old known issue. See here

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#446

According to the decisions of the committee, in your example, the ?: Operator should always return a temporary object, which means that for the my_other_s branch my_other_s original my_other_s object should be copied. This is why the compiler requires a copy constructor.

This language does not yet exist in C ++ 03, but many compilers have implemented this approach from the very beginning.

+4
source

As for your updated question, if modifying the definition of S allowed, the following work might help:

 struct S { ... S& ref() { return *this; } // additional member function ... }; (some_condition ? S().ref() : my_other_S).f(); 

Hope this helps

+1
source

Source: https://habr.com/ru/post/1345795/


All Articles