在C++中创建对三元运算符结果的const引用是否安全?

nev*_*stn 23 c++ gcc ternary-operator c++11 clang++

这段代码中有一些非常明显的事情:

float a = 1.;

const float & x = true ? a : 2.; // Note: `2.` is a double

a = 4.;

std::cout << a << ", " << x;
Run Code Online (Sandbox Code Playgroud)

clang和gcc输出:

4, 1
Run Code Online (Sandbox Code Playgroud)

人们会天真地期望两次打印相同的值,但事实并非如此.这里的问题与参考无关.有一些有趣的规则决定了它的类型? :.如果两个参数的类型不同并且可以进行转换,则它们将使用临时的.该引用将指向临时的? :.

上面的示例编译得很好,在编译时可能会也可能不会发出警告,-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;
}
Run Code Online (Sandbox Code Playgroud)

如果您的逻辑在此代码之后假定a[0]将反映任何更改result,则在?:创建临时的情况下将是错误的.此外,如果在某些时候你制作了一个指针result并且你在result超出范围之后使用它,那么尽管你的原始版本a没有超出范围,但会出现分段错误.

我觉得有充分的理由不使用此形式超出此处提到的"可维护性和阅读问题" ,尤其是在编写模板化代码时,您的某些类型和它们的常量可能无法控制.

所以我的问题是,const &在三元运算符上使用s 是否安全?

PS Bonus示例1,额外的并发症(另见此处):

float a = 0;
const float b = 0;
const float & x = true ? a : b;

a = 4;
cout << a << ", " << x;
Run Code Online (Sandbox Code Playgroud)

铿锵输出:

4, 4
Run Code Online (Sandbox Code Playgroud)

gcc 4.9.3输出:

4, 0
Run Code Online (Sandbox Code Playgroud)

使用clang这个例子编译并按预期运行,但最新版本的gcc(

PS2奖金示例2,非常适合采访;):

double a = 3;

const double & a_ref = a;

const double & x = true ? a_ref : 2.;

a = 4.;

std::cout << a << ", " << x;
Run Code Online (Sandbox Code Playgroud)

输出:

4, 3
Run Code Online (Sandbox Code Playgroud)

M.M*_*M.M 9

首先,条件运算符的结果是指定所选操作数的glvalue,或者其值来自所选操作数的prvalue.

TC注意到的异常:如果至少有一个操作数属于类类型并且具有转换为引用操作符,则结果可以是指定由该操作符的返回值指定的对象的左值; 如果指定的对象实际上是临时的,则可能会产生悬空参考.对于这样的运算符来说,这是一个问题,它提供了prvalues到lvalues的隐式转换,而不是条件运算符本身引入的问题.

在这两种情况下,绑定对结果的引用是安全的,将引用绑定到左值或prvalue的常用规则适用.如果引用绑定到prvalue(条件的prvalue结果,或者从条件的左值结果初始化的prvalue),则prvalue的生命周期被扩展以匹配引用的生命周期.


在您的原始情况下,条件是:

true ? a : 2.
Run Code Online (Sandbox Code Playgroud)

第二个和第三个操作数是:"lvalue of type float"和"prvalue of type double".这是cppreference摘要中的案例5,结果是"prvalue of type double".

然后,您的代码使用不同(非引用相关)类型的prvalue初始化const引用.这样做的行为是复制初始化与引用相同类型的临时.

总之,之后const float & x = true ? a : 2.;,x是一个左值表示一个float其值是转换的结果adouble和背部.(不确定是否保证比较等于a). x不受约束a.


在奖励情况1中,条件运算符的第二和第三操作数是"类型的左值float"和"类型的左值const float".这是同一个cppreference链接的情况3,

两者都是相同值类别的glvalues,并且除了cv-qualification之外具有相同的类型

行为是第二​​个操作数被转换为"lvalue of type const float"(表示相同的对象),条件的结果是"lvalue of type const float"表示所选对象.

然后绑定const float &到"lvalue of type const float",它直接绑定.

所以之后const float & x = true ? a : b;, x直接绑定到任何一个ab.


在奖金案例2中,true ? a_ref : 2. .第二个和第三个操作数是"类型的左值const double"和"类型的首值double",因此结果是"类型的首值double".

然后你绑定它const double & x,这是一个直接绑定,因为const double与引用相关double.

所以在之后const double & x = true ? a_ref : 2.; ,则x是一个左值,表示一个具有相同值a_ref (但x不受约束a)的double .