Consider a method that returns a std::string_view
either from a method that returns a const std::string&
or from an empty string. To my surprise, writing the method this way results in a dangling string view:
const std::string& otherMethod();
std::string_view myMethod(bool bla) {
return bla ? otherMethod() : ""; // Dangling view!
}
https://godbolt.org/z/1Hu_p2
It seems that the compiler first puts a temporary std::string
copy of the result of otherMethod()
on the stack and then returns a view of this temporary copy instead of just returning a view of the reference. First I thought about a comipler bug, but both G++ and clang do this.
The fix is easy: Wrapping otherMethod
into an explicit construction of string_view
solves the issue:
std::string_view myMethod(bool bla) {
return bla ? std::string_view(otherMethod()) : ""; // Works as intended!
}
https://godbolt.org/z/Q-sEkr
Why is this the case? Why does the original code create an implicit copy without warning?
The std::string_view, from the C++17 standard, is a read-only non-owning reference to a char sequence. The motivation behind std::string_view is that it is quite common for functions to require a read-only reference to an std::string-like object where the exact type of the object does not matter.
So, frequently, you can pass string_view by reference and get away with it. But you should pass string_view by value, so that the compiler doesn't have to do those heroics on your behalf. And so that your code-reviewer doesn't have to burn brain cells pondering your unidiomatic decision to pass by reference.
Because that's how the conditional operator works.
You're invoking ?:
on two operands, one of which is an lvalue of type std::string const
and the other is an lvalue of type char const[1]
. The language rule for the conditional operator is... really complicated. The relevant rule is:
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 typeT1
to a target type related to the typeT2
of the operand expressionE2
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
andT2
are the same class type [...]- otherwise, if
T2
is a base class ofT1
, [...]- 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 ]
Can't convert std::string const
to either char const(&)[1]
or char const*
, but you can convert char const[1]
to std::string const
(the inner nested bullet)... so that's what you get. A prvalue of type std::string const
. Which is to say, you're either copying one string or constructing a new one... either way, you're returning a string_view
to a temporary which goes out of scope immediately.
What you want is either what you had:
std::string_view myMethod(bool bla) {
return bla ? std::string_view(otherMethod()) : "";
}
or:
std::string_view myMethod(bool bla) {
return bla ? otherMethod() : ""sv;
}
The result of that conditional operator is a string_view
, with both conversions being safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With