请考虑以下代码:
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无法编译,说:
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
我不明白为什么要在该行上进行复制构建 - 目的是简单地在默认构造的f()
实例或S
上调用my_other_S
,即它应该相当于:
if (some_condition)
S().f();
else
my_other_S.f();
第一种情况有什么不同,为什么需要复制构造函数?
编辑:那么,有没有办法在表达式上下文中表达“对预先存在的对象上的临时执行此操作”?
答案 0 :(得分:9)
?:
的结果是rvalue,一个新对象,如果其中一个参数是rvalue。要创建此rvalue,编译器必须复制结果。
if (some_condition)
S().f(); // Compiler knows that it's rvalue
else
my_other_S.f(); // Compiler knows that it's lvalue
这与你不能做的原因相同
struct B { private: B(const B&); };
struct C { C(B&); C(const B&); };
int main() {
B b;
C c(some_condition ? b : B());
}
我改变了我的榜样,因为旧的有点糟透了。你可以清楚地看到这里没有办法编译这个表达式,因为编译器无法知道要调用的构造函数。当然,在这种情况下,编译器可以将两个参数强制转换为const B&
,但由于某些不太相关的原因,它不会。
编辑:不,没有,因为没有办法编译该表达式,因为关于它的重要数据(rvalue或lvalue)在运行时会有所不同。编译器尝试通过复制构造转换为rvalue来解决此问题,但它不能因为它无法复制而无法编译。
答案 1 :(得分:7)
来自[expr.cond]
(来自n3242草案的措辞):
否则,如果第二个和第三个操作数具有不同的类型并且具有(可能是cv限定的)类类型,或者两者都是相同值类别的glvalues和除cv-qualification之外的相同类型,则尝试将每个操作数转换为另一个操作数的类型。确定类型
E1
的操作数表达式T1
是否可以转换为匹配类型E2
的操作数表达式T2
的过程定义如下:
- 如果
E2
是左值,则E1
可以转换为匹配E2
,如果E1
可以隐式转换(第4条)到类型“左值引用{ {1}}“,受限于在转换中,引用必须直接绑定(8.5.3)到左值。- 如果
T2
是xvalue:E2
可以转换为匹配E1
,如果E2
可以隐式转换为“E1
的右值引用” “,受限于引用必须直接绑定。如果
T2
是右值,或者上述两个转换都不能完成且至少有一个操作数具有(可能是cv-qualified)类类型:
- 如果
E2
和E1
具有类类型,并且基础类类型相同或者一个是另一个的基类:E2
可以转换为匹配{{1如果E1
的类与E2
的类相同,或者是T2
的类的基类,则T1
的cv-qualification与cv-qualification相同,或者比T2
的cv资格更高的cv资格。如果应用了转换,则通过从T1
复制初始化E1
类型的临时值并将该临时值用作转换后的操作数,将T2
更改为类型T2
的prvalue
此规则提到了复制初始化,但它不适用,因为两个操作数具有相同的类型
如果第二个和第三个操作数是相同值类别的glvalues并且具有相同的类型,则结果 属于该类型和值类别,如果第二个或第三个操作数是位字段,则它是位字段,或者如果 两者都是比特字段。
此规则不适用,因为E1
是左值而S()
是左值。
否则,结果是prvalue。如果第二个和第三个操作数不具有相同的类型,并且具有(可能是cv限定的)类类型,则使用重载决策来确定要应用于操作数的转换(如果有)(13.3.1.2,13.6) 。如果重载决策失败,则程序格式错误。否则,应用如此确定的转换,并使用转换的操作数代替原始操作数 本节其余部分的操作数。 Lvalue-to-rvalue(4.1),array-to-pointer(4.2)和函数到指针(4.3)标准转换在第二个和第三个操作数上执行。在这些转换之后,以下之一应该成立:
- 第二个和第三个操作数具有相同的类型;结果是那种类型。如果操作数具有类类型,结果是结果类型的prvalue临时值,它是从第二个操作数或第三个操作数复制初始化,具体取决于第一个操作数的值。
应用此规则,结果是复制初始化(强调我的)。
答案 2 :(得分:4)
这是一个古老的已知问题。见这里
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#446
根据委员会的决定,在您的示例中,?:
运算符应始终返回临时对象,这意味着对于my_other_s
分支,必须复制原始my_other_s
对象。这就是编译器需要复制构造函数的原因。
这种语言还没有在C ++ 03中,但许多编译器从一开始就实现了这种方法。
答案 3 :(得分:1)
至于您更新的问题,如果修改S
的定义是
允许,以下解决方案可能会有所帮助:
struct S
{
...
S& ref() { return *this; } // additional member function
...
};
(some_condition ? S().ref() : my_other_S).f();
希望这有帮助