Considering:
void foo(std::string& s);
Inside this function, the expression s is lvalue std::string (not std::string&), because references don't really "exist" in expressions:
[expr.type/1]: If an expression initially has the type “reference toT” ([dcl.ref], [dcl.init.ref]), the type is adjusted toTprior 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. [..]
Now consider:
const std::string& foo(const std::string& s1, const std::string& s2)
{
return (s1.size() < s2.size() ? s1 : s2);
}
There was a debate on another question about whether the conditional operator here involves the creation of a temporary (which then has ramifications about the return value for foo being a dangling reference).
My interpretation was that, yes, it must, because:
[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.
and:
[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.
Initialising a std::string from a std::string involves a copy.
However, I was surprised that GCC didn't warn on the dangling reference. Investigating, I found that foo indeed does propagate the reference semantics for the selected argument:
#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)
The original s2, passed by reference into foo, has been selected by the conditional operator, then bound to s3, and modified. There is no evidence of any copying going on.
This doesn't match my reading of how expressions work and of how the conditional operator works.
So, which of my above statements is incorrect, and why?
Since there seems to be some confusion, below I have diagrammed what my understanding says is the chain of events. I realise that it's wrong — my testcase above proves that. But I'd like to understand exactly why. Ideally I'd like some standard wording, not just "you're wrong". I already know I'm wrong. That's why I'm asking.
- References to strings passed into function
- Expression evaluated containing conditional operator
- The latter two operands are lvalue expressions of type
const std::string(not references!) - The latter two operands have the same type and value category, so the conditional operator's result is
const std::string, too
- The latter two operands are lvalue expressions of type
- The result of the expression is initialised from the selected operand; we've already established that the operands and the result type are
const std::string, so it's aconst std::stringinitialised from aconst std::string - The expression, as one that initialises an object, has value category rvalue (and I believe this implies the object is also a temporary?)
- Then we initialise the function's return value from that temporary; this is evil as the return type is a reference, so we dangle.