C++ 不要求 (cond ? string_1 : string_2) 初始化一个字符串吗?

Doesn't C++ mandate that (cond ? string_1 : string_2) initialize a string?

正在考虑:

void foo(std::string& s);

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

[expr.type/1]: If an expression initially has the type “reference to T ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. [..]

现在考虑:

const std::string& foo(const std::string& s1, const std::string& s2)
{
    return (s1.size() < s2.size() ? s1 : s2);
}

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

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

[expr.cond/5]: If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.

和:

[expr.cond/7.1]: The second and third operands have the same type; the result is of that type and the result object is initialized using the selected operand.

std::string 初始化 std::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
}

(live demo)

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

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

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


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

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

从你引用的那部分开始:

If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.

第二个和第三个操作数都是std::string const类型的左值,所以结果是std::string const.

类型的左值

Initialising a std::string from a std::string involves a copy.

但我们不是从 std::string 初始化 std::string。在:

const std::string& foo(const std::string& s1, const std::string& s2)
{
    return (s1.size() < s2.size() ? s1 : s2);
}

我们正在从 std::string const 类型的左值初始化 std::string const&。那只是一个直接引用绑定。无需复制。

我的误解似乎源于我的“图表”中的第 3 步:我引用的关于初始化结果的措辞 ([expr.cond/7.1]) 不适用;它在 “否则,结果是一个 prvalue” 子句。我错过了。

所以,实际上这里没有讨论关于我们的条件运算符表达式的初始化。因此,不会创建新对象,如果不存在这样的对象,则它不能是临时对象。

那么,我们得到的唯一描述是:

[expr.cond/1]: [..] the result of the conditional expression is the value of the second expression, otherwise that of the third expression.

我实际上认为这不是最清晰的措辞,但与类似的措辞相比,例如内置下标运算符的规则(return 不是引用类型,但它的结果是它的两个操作数引用的“值”),它看起来确实足够明确,以至于整个表达式在这里“ "原始字符串之一。