C++ 不是强制要求 (cond ? string_1 : string_2) 初始化字符串吗?

Ast*_*ngs 5 c++ language-lawyer c++17

考虑:

void foo(std::string& s);
Run Code Online (Sandbox Code Playgroud)

在这个函数中,表达式s左值 std::string不是 std::string&),因为引用在表达式中并不真正“存在”:

[expr.type/1]如果表达式最初具有“引用T”类型([dcl.ref],[dcl.init.ref]),T在任何进一步分析之前将类型调整为。表达式指定引用所表示的对象或函数,表达式是左值或 x 值,具体取决于表达式。[..]

现在考虑:

const std::string& foo(const std::string& s1, const std::string& s2)
{
    return (s1.size() < s2.size() ? s1 : s2);
}
Run Code Online (Sandbox Code Playgroud)

关于这里的条件运算符是否涉及创建临时对象的另一个问题存在争论(然后对foo作为悬空引用的返回值产生影响)。

我的解释是,是的,它必须,因为:

[expr.cond/5]: 如果第二个和第三个操作数是同一个值类别的泛左值并且具有相同的类型,则结果属于那个类型和值类别,如果第二个或第三个操作数是位域,则结果是位域,或者如果两者都是位域。

和:

[expr.cond/7.1]:第二个和第三个操作数的类型相同;结果属于该类型,结果对象使用选定的操作数进行初始化

std::string从 a初始化 astd::string涉及一个副本。

然而,我很惊讶 GCC 没有对悬空引用发出警告。调查中,我发现foo确实传播所选参数参考语义:

#include <string>
#include <iostream>

using std::string;
using std::cout;

void foo(string& s1, string& s2)
{
    auto& s3 = (s1.size() < s2.size() ? s1 : s2);
    s3 = "what";
}

int main()
{
    string s1 = "hello";
    string s2 = "world";
    
    foo(s1, s2);
    
    cout << s1 << ' ' << s2 << '\n';   // Output: hello what
}

Run Code Online (Sandbox Code Playgroud)

现场演示

s2通过引用传递到的原始foo已由条件运算符选择,然后绑定到s3并进行修改。没有任何复制的证据。

这与我对表达式如何工作以及条件运算符如何工作的理解不符。

那么,我上面的哪些陈述是不正确的,为什么?


由于似乎有些混乱,因此我在下面绘制了我的理解所说的事件链。我意识到这是错误的——我上面的测试用例证明了这一点。但我想确切地了解为什么。理想情况下,我想要一些标准的措辞,而不仅仅是“你错了”。我已经知道我错了。这就是为什么我要问。

  1. 对传递给函数的字符串的引用
  2. 评估包含条件运算符的表达式
    • 后两个操作数是左值表达式类型const std::string不是引用!)
    • 后两个操作数具有相同的类型和值类别,因此条件运算符的结果const std::string也是
  3. 表达式的结果从选定的操作数初始化;我们已经确定操作数和结果类型是const std::string,所以它是const std::string从 a 初始化的const std::string
  4. 作为初始化对象的表达式,其值类别为 rvalue(我相信这意味着该对象也是临时对象?)
  5. 然后我们从那个临时值初始化函数的返回值;这是邪恶的,因为返回类型是一个引用,所以我们悬而未决。