0

In the code below, why does the first call to g resolve to the reference version, even though s is already an rvalue reference?

#include <string>
#include <iostream>
#include <utility>

void g(const std::string& s) {
    std::cout << "g(const std::string&)\n";
}

void g(std::string&& s) {
    std::cout << "g(std::string&&)\n";
}

void f(std::string&& s) {
    static_assert(std::is_same<decltype(s), std::string&&>::value,
                  "s is not std::string&&");
    static_assert(!std::is_same<decltype(s), std::string&>::value,
                  "s magically changed to std::string&");
    g(s);
    g(std::move(s));
}

int main() {
    f("abc");
    return 0;
}

Output:

g(const std::string&)
g(std::string&&)

As the static_asserts show, s hasn't magically started being treated as an lvalue reference in the body of the function, as I've seen some people assert (or maybe I misunderstood what they were saying).

(Note: After some investigation I think I've come up with a reasonable answer; but I'm completely not sure if it's accurate. So, I'd like to post my proposed answer and get comments on it. Sorry if this isn't the right way to ask whether my answer is correct - but ironically, meta.stackoverflow.com tells me I can't ask questions on asking questions until I've already asked some questions.)

Daniel Schepler
  • 3,043
  • 14
  • 20
  • I don't see anything in http://stackoverflow.com/questions/14351669/rvalue-reference-why-arent-rvalues-implicitly-moved which addresses my specific question, which is about the mechanics of how a compiler does the overload resolution, rather than why it's set up to work this way. - Briefly, my proposed answer would have been "because s is a variable, overload resolution starts from std::string&& & which collapses to std::string&" whereas std::move(s) is syntactically a temporary, so overload resolution starts from std::string&& && which collapses to std::string&&. – Daniel Schepler May 06 '16 at 18:06
  • "As the static_asserts show, s hasn't magically started being treated as an lvalue" -- It does appear to show that, but doesn't. When the operand of `decltype` is a simple name, `decltype` gives it special treatment and detects how the name is declared. `decltype((s))` (not a simple name) will give a different result from `decltype(s)`. –  May 06 '16 at 18:13
  • @hvd I see, indeed `static_assert(std::is_same, "s as expr is not std::string&")` does pass. – Daniel Schepler May 06 '16 at 18:22
  • 1
    I think that C++11 Section 5 paragraphs 5 and 6 explain the mechanics of why the compiler must perform the overload resolution this way. It summaries things in a note that concludes: "In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues...". Unfortunately, this can't be posted as an answer since the question has been closed. The duplicate question/answer explains why this rule exists. – Michael Burr May 06 '16 at 18:30
  • OK, I think the answer I'd accept would be that the special handling of decltype(name) was misleading me, and in any actual use of s in expressions it would be treated identically to std::string&. Though Michael Burr's answer also gives some insight - and what I suggested might be a possible internal implementation detail of a compiler, but only if it can be verified it never produces different behavior from the standard, which I don't have deep enough knowledge to tell whether it's the case or not. – Daniel Schepler May 06 '16 at 19:40

0 Answers0