显式将prvalue转为const引用以避免三元运算符额外拷贝是否合法?
关于三元运算符结合const引用的合法性与编译指令复杂度问题
假设我们有一个不可拷贝且不可移动的类型Foo,以及如下函数:
int f(const Foo& foo) { ... // 根据foo的值计算结果 }
当尝试直接用三元运算符传递参数时:
const Foo foo1{ 42 }; bool condition = getCondition(); int result = f(condition ? foo1 : Foo{ 150 });
由于foo1是左值,Foo{150}是prvalue,三元运算符会将结果推导为Foo类型的prvalue,这意味着当condition为true时需要拷贝foo1,而Foo不可拷贝,导致编译错误。
通过显式转换为const引用,改写后的代码如下:
const Foo foo1{ 42 }; bool condition = getCondition(); int result = f(condition ? foo1 : static_cast<const Foo&>(Foo{ 150 }));
此时三元运算符返回const Foo&类型,无需拷贝/移动即可编译通过。当存在多个条件参数时,这种写法相比嵌套调用更简洁:
// 冗余的嵌套写法 int result = condition1 ? (condition2 ? f(foo1, foo2) : f(foo1, Foo{ 222 })) : (condition2 ? f(Foo{ 111 }, foo2) : f(Foo{ 111 }, Foo{ 222 })); // 简洁的显式转换写法 int result = f( condition1 ? foo1 : static_cast<const Foo&>(Foo{ 111 }), condition2 ? foo2 : static_cast<const Foo&>(Foo{ 222 }) );
针对这一写法的两个问题,解答如下:
1. 显式转换的写法是否合法?
合法。原因如下:
- 三元运算符的两个操作数分别是
const Foo&(绑定到foo1)和const Foo&(通过static_cast将临时对象转换为左值引用),因此三元运算符的结果类型为const Foo&。 - 根据C++标准,临时对象的生命周期会延续到创建它的完整表达式结束时。这里的完整表达式是
f(...)的整个调用表达式,因此临时对象Foo{111}或Foo{222}会一直存活到f函数执行完毕,引用不会悬空,完全符合标准要求。
注:虽然有讨论指出三元运算符本身不会延长临时对象生命周期,但此处临时对象的生命周期是由完整表达式的规则保证的,与三元运算符无关,因此不存在悬空风险。
2. N个条件参数时,编译器生成的指令是*O(N)还是O(2^N)*级别?
编译器生成的指令是*O(N)*级别。
- 嵌套调用的写法中,每个条件都会组合出不同的
f调用分支,N个条件会产生2^N个不同的f调用实例,指令规模是指数级的。 - 显式转换的写法中,
f仅被调用一次,每个参数的三元判断是独立的条件分支。编译器会为每个参数生成1次条件判断逻辑,总共有N个条件分支,再加上一次f的调用,因此指令规模是线性的O(N)。
内容的提问来源于stack exchange,提问作者Alexander Belous
相关产品推荐
相关产品推荐

