这段代码中有一些非常明显的事情:
float a = 1.;
const float & x = true ? a : 2.; // Note: `2.` is a double
a = 4.;
std::cout << a << ", " << x;
clang和gcc输出:
4, 1
人们会天真地期望两次印刷相同的价值,但事实并非如此。这里的问题与参考无关。有一些有趣的规则规定了? :
的类型。如果两个参数的类型不同并且可以进行转换,则它们将使用临时的。引用将指向临时? :
。
上面的示例编译得很好,在使用-Wall
进行编译时可能会也可能不会发出警告,具体取决于编译器的版本。
以下是一个关于在看似合法的代码中容易出错的示例:
template<class Iterator, class T>
const T & min(const Iterator & iter, const T & b)
{
return *iter < b ? *iter : b;
}
int main()
{
// Try to remove the const or convert to vector of floats
const std::vector<double> a(1, 3.0);
const double & result = min(a.begin(), 4.);
cout << &a[0] << ", " << &result;
}
如果此代码后的逻辑假定a[0]
上的任何更改都会反映到result
,则?:
创建临时更改时会出错。此外,如果您在某个时刻指向result
,并且在result
超出范围后使用它,则会出现分段错误,尽管您的原始a
没有&# 39;超出范围。
我觉得有严重的理由不使用这种形式超出&#34;可维护性和阅读问题&#34;提到here,特别是在编写模板化代码时,您的某些类型及其常规可能无法控制。
所以我的问题是,在三元运算符上使用const &
是否安全?
P.S。奖金示例1,额外的并发症(另见here):
float a = 0;
const float b = 0;
const float & x = true ? a : b;
a = 4;
cout << a << ", " << x;
clang输出:
4, 4
gcc 4.9.3输出:
4, 0
使用clang这个例子编译并按预期运行,但最新版本的gcc(
P.S.2奖金示例2,非常适合采访;):
double a = 3;
const double & a_ref = a;
const double & x = true ? a_ref : 2.;
a = 4.;
std::cout << a << ", " << x;
输出:
4, 3
答案 0 :(得分:9)
首先,条件运算符的结果是指定所选操作数的glvalue,或者是来自所选操作数的值的prvalue。
如T.C所述的异常:如果至少有一个操作数属于类类型并且具有转换为引用运算符,则结果可以是指定由该运算符的返回值指定的对象的左值;如果指定的对象实际上是临时的,则可能会产生悬空参考。对于这样的运算符来说,这是一个问题,它提供了prvalues到lvalues的隐式转换,而不是条件运算符本身引入的问题。
在这两种情况下,绑定对结果的引用是安全的,将引用绑定到左值或prvalue的常用规则适用。如果引用绑定到prvalue(条件的prvalue结果,或者从条件的左值结果初始化的prvalue),则prvalue的生命周期被扩展以匹配引用的生命周期。
在您的原始案例中,条件是:
true ? a : 2.
第二个和第三个操作数是:&#34;类型float
的左值&#34;和&#34;类型为double
&#34;的prvalue。这是cppreference summary中的案例5,其结果为&#34;类型为double
&#34的prvalue。
然后,您的代码使用不同(非引用相关)类型的prvalue初始化const引用。这样做的行为是复制初始化与引用相同类型的临时。
总之,在const float & x = true ? a : 2.;
之后,x
是左值,表示float
,其值是将a
转换为double
并返回的结果。 (不确定是否保证比较等于a
)。 x
未绑定a
。
在奖励案例1中,条件运算符的第二个和第三个操作数是&#34;类型float
的左值&#34;和&#34;左const float
&#34;的左值。这是同一个cppreference链接的案例3,
两者都是相同值类别的glvalues,并且除了cv-qualification
之外具有相同的类型
行为是第二个操作数被转换为&#34;类型为const float
&#34的左值; (表示相同的对象),条件的结果是&#34;类型const float
的左值&#34;表示所选对象。
然后将const float &
绑定到&#34;类型为const float
&#34;的左值,它直接绑定。
因此,在const float & x = true ? a : b;
之后,x
会直接绑定到a
或b
。
在奖金案例2中,true ? a_ref : 2.
。第二个和第三个操作数是const double
&#34;类型的左值;和&#34;类型为double
&#34;的prvalue,因此结果为&#34;类型为double
&#34;的prvalue。
然后将其绑定到const double & x
,这是一个直接绑定,因为const double
与double
的引用相关。
因此,在const double & x = true ? a_ref : 2.;
之后,x
是一个左值,表示与a_ref
具有相同值的double(但x
未绑定到a
)
答案 1 :(得分:3)
简而言之:是的,它可以是安全的。但你需要知道会发生什么。
Lvalue const引用和rvalue引用可用于延长临时变量的生命周期(减去下面引用的异常)。
顺便说一句,我们已经从您的previous question中了解到gcc 4.9系列并不是此类测试的最佳参考。使用gcc 6.1或5.3编译的奖励示例1给出了与使用clang编译的完全相同的结果。正如它应该的那样。
来自N4140的报价(选定的片段):
[class.temporary]
有两种情况下,临时状态被摧毁 不同于完整表达的结束点。 [...]
第二个上下文是引用绑定到临时的。该 临时引用的临时或临时的 引用绑定到的子对象的完整对象 在参考文件的生命周期内持续存在,除了:[无相关内容 这个问题的条款]
[expr.cond]
3)否则,如果第二个和第三个操作数有不同的类型和 要么具有(可能是cv-qualified)类类型,要么两者都是glvalues 相同值类别和相同类型除外 cv-qualification,尝试转换每个操作数 到另一种的类型。
如果
E2
是左值,则E1
可以转换为匹配E2
,如果E1
可以隐式转换(第4条)到“左值”类型参考 到T2
“,受到转换中约束的约束 引用必须直接绑定到左值[...]
如果
E2
是prvalue,或者上述转换都不能完成且至少有一个操作数具有(可能是cv-qualified) 班级类型:
- 否则(即,如果
E1
或E2
具有非类型类型,或者它们都具有类类型但基础类不是 相同或一个是另一个的基类):E1
可以转换为匹配E2
如果E1
可以隐式转换为表达式的类型E2
如果将E2
转换为prvalue(或类型),则会E2
如果float a = 1.; const float & x = true ? a : 2.; // Note: `2.` is a double a = 4.; std::cout << a << ", " << x;
是prvalue,则有,[...]如果两者都不能转换,则操作数保持不变 进一步检查如下所述。如果只是一个 转换是可能的,转换应用于所选 操作数和转换后的操作数用于代替原始操作数 本节其余部分的操作数。
4)如果第二个和第三个操作数是相同值的glvalues 类别和具有相同的类型,结果是该类型和值 类别[...]
5)否则,结果是prvalue。如果是第二个和第三个 操作数不具有相同的类型,并且(可能是) cv-qualified)类类型[...]。否则,转换就这样了 确定应用,并使用转换后的操作数 本节其余部分的原始操作数。
6)Lvalue-to-rvalue,array-to-pointer和function-to-pointer 标准转换在第二和第三个操作数上执行。 在这些转换之后,以下之一应该成立:
- 第二个和第三个操作数具有算术或枚举类型;执行通常的算术转换以将它们带到a 常见类型,结果属于该类型。
所以第一个例子很明确地完成了你所经历的事情:
x
float
是绑定到a
类型的临时对象的引用。它没有引用true ? float : double
,因为表达式double
被定义为产生double
- 然后您才将float
转换回新的x
将float a = 0;
const float b = 0;
const float & x = true ? a : b;
a = 4;
cout << a << ", " << x;
分配给a
时不同b
。
在你的第二个例子中(奖金1):
x
三元运算符不必在a
和a
之间进行任何转换(匹配cv限定符除外)并且它产生一个引用const浮点值的左值。 double a = 3;
const double & a_ref = a;
const double & x = true ? a_ref : 2.;
a = 4.;
std::cout << a << ", " << x;
别名E1
并且必须反映对E2
所做的更改。
在第三个例子中(奖金2):
E1
在这种情况下,如果E2
可以隐式转换为[...] [E2
]的类型,则 a
可以转换为匹配x
如果a
是prvalue ,则有。现在,该prvalue具有与{{1}}相同的值,但是它是一个不同的对象。 {{1}}没有别名{{1}}。
答案 2 :(得分:0)
在C ++中创建对三元运算符结果的const引用是否安全?
作为Asker,我会将讨论总结为;对于非模板化的代码,在非常现代的编译器上,可以使用警告。对于模板化代码,作为代码审阅者,我会一般不鼓励它。