为什么 std::string_view 在三元表达式中创建悬空视图?

Why does std::string_view create a dangling view in a ternary expression?

考虑一个方法 returns a std::string_view 来自 returns a const std::string& 的方法或空字符串。令我惊讶的是,以这种方式编写方法会导致悬空字符串视图:

const std::string& otherMethod();

std::string_view myMethod(bool bla) {
    return bla ? otherMethod() : ""; // Dangling view!
}

https://godbolt.org/z/1Hu_p2

似乎编译器首先将 otherMethod() 结果的临时 std::string 副本放在堆栈上,然后 returns 这个临时副本的视图而不是只返回一个参考的看法。首先我想到了一个编译器错误,但是 G++ 和 clang 都这样做。

修复很简单:将 otherMethod 包装到 string_view 的显式构造中解决了问题:

std::string_view myMethod(bool bla) {
    return bla ? std::string_view(otherMethod()) : ""; // Works as intended!
}

https://godbolt.org/z/Q-sEkr

为什么会这样?为什么原始代码会在没有警告的情况下创建隐式副本?

因为条件运算符就是这样工作的。

您在两个操作数上调用 ?:,其中一个是 std::string const 类型的左值,另一个是 char const[1] 类型的左值。条件运算符的语言规则……真的很复杂。 relevant rule 是:

Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to form an implicit conversion sequence from each of those operands to the type of the other. [ Note: Properties such as access, whether an operand is a bit-field, or whether a conversion function is deleted are ignored for that determination. — end note ] Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:

  • If E2 is an lvalue, the target type is “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly ([dcl.init.ref]) to a glvalue.
  • If E2 is an xvalue, [...]
  • If E2 is a prvalue or if neither of the conversion sequences above can be formed and at least one of the operands has (possibly cv-qualified) class type:

    • if T1 and T2 are the same class type [...]
    • otherwise, if T2 is a base class of T1, [...]
    • otherwise, the target type is the type that E2 would have after applying the lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions.

Using this process, it is determined whether an implicit conversion sequence can be formed from the second operand to the target type determined for the third operand, and vice versa. If both sequences can be formed, or one can be formed but it is the ambiguous conversion sequence, the program is ill-formed. If no conversion sequence can be formed, the operands are left unchanged and further checking is performed as described below. Otherwise, if exactly one conversion sequence can be formed, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this subclause. [ Note: The conversion might be ill-formed even if an implicit conversion sequence could be formed. — end note ]

无法将 std::string const 转换为 char const(&)[1]char const*,但您 可以将 char const[1] 转换为 std::string const(内部嵌套项目符号)...这就是您得到的。 std::string const 类型的纯右值。也就是说,您要么复制一个字符串,要么构造一个新字符串……无论哪种方式,您都将 string_view 返回到一个立即超出范围的临时文件。


你想要的就是你拥有的:

std::string_view myMethod(bool bla) {
    return bla ? std::string_view(otherMethod()) : "";
}

或:

std::string_view myMethod(bool bla) {
    return bla ? otherMethod() : ""sv;
}

该条件运算符的结果是 string_view,两种转换都是安全的。