Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a copy constructor required 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 fails to compile this, 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 don't see why copy construction should be taking place on that line - the intention is to simply call f() on either a default-constructed S instance, or on my_other_S, i.e. it should be equivalent to:

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

What is different in the first case and why is a copy constructor required?

EDIT: Is there any way, then, to express "perform this operation on either a temporary on a preexisting object" in an expression context?

like image 297
HighCommander4 Avatar asked Mar 29 '11 22:03

HighCommander4


People also ask

Why is copy constructor required?

A user-defined copy constructor is generally needed when an object owns pointers or non-shareable references, such as to a file, in which case a destructor and an assignment operator should also be written (see Rule of three).

What is copy constructor and its use?

The copy constructor is a constructor which creates an object by initializing it with an object of the same class, which has been created previously. The copy constructor is used to − Initialize one object from another of the same type. Copy an object to pass it as an argument to a function.

What is the purpose of copy constructor to demonstrate in Java?

In Java, a copy constructor is a special type of constructor that creates an object using another object of the same Java class. It returns a duplicate copy of an existing object of the class. We can assign a value to the final field but the same cannot be done while using the clone() method.

Why are we required to pass a value to a copy constructor by reference?

It is necessary to pass object as reference and not by value because if you pass it by value its copy is constructed using the copy constructor. This means the copy constructor would call itself to make copy. This process will go on until the compiler runs out of memory.


2 Answers

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

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

This is for the same reason that you can't 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 bit suck. You can clearly see here there is no way to compile this expression because the compiler can't know what constructor to call. Of course, in this case, the compiler could coerce both arguments to const B&, but for some reason which is not very relevant, it won't.

Edit: No, there isn't, because there's no way to compile that expression, as important data about it (rvalue or lvalue) varies at run-time. The compiler tries to fix this problem for you by converting to rvalue by copy constructing, but it can't because it can't copy, so it can't compile.

like image 197
Puppy Avatar answered Sep 30 '22 13:09

Puppy


From [expr.cond] (wording from draft n3242):

Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:

  • If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to an lvalue.
  • If E2 is an xvalue: E1 can be converted to match E2 if E1 can be implicitly converted to the type “rvalue reference to T2”, subject to the constraint that the reference must bind directly.
  • If E2 is an rvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:

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

This rule mentions copy-initialization, but it does not apply since both operands have the same type

If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category and it is a bit-field if the second or the 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 is a prvalue. If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands (13.3.1.2, 13.6). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this section. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are per-formed on the second and third operands. After those conversions, one of the following shall hold:

  • The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.

This rule is applied, the result is copy initialized (emphasis mine).

like image 21
Ben Voigt Avatar answered Sep 30 '22 14:09

Ben Voigt