Why binding rvalue to a const type makes it an lvalue?

105 Views Asked by At

This question may not have the best title but here is the code that will explain what I am trying to ask.

This code runs and prints "lvalue" but If I remove const from MyPair's first type, it gives me the expected output, which is "rvalue". I would like to know what role const plays here?

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


using MyPair = std::pair<const std::string, int>;


void func(std::string&& str)
{
    std::cout << "rvalue" << std::endl;
}

void func(const std::string& str)
{
    std::cout << "lvalue" << std::endl;
}

template<typename T>
void func_forward(T&& p)
{
    func(std::forward<T>(p).first);
}

void test(MyPair&& p)
{
    func_forward(std::move(p));
}

int main()
{
    test({"hello", 3});
    return 0;
}
2

There are 2 best solutions below

0
user17732522 On BEST ANSWER

Nothing is being turned into a lvalue.

std::forward<T>(p).first is in your example's instantiation is a rvalue (specifically xvalue) regardless of the const. However, it's type will be const std::string or std::string depending on the choice of const in the MyPair type.

The problem is that your method of differentiating lvalues from rvalues with func is incorrect, because the first overload of func's parameter is not const-qualified and so will only accept rvalues of non-const std::string type.

Your second func overload on the other hand can accept any value category and const-qualification because a const lvalue reference is allowed to bind to lvalues as well as rvalues. So it will be chosen if first is const qualified, regardless of value category as the the only viable overload.

To fix your detection method use const std::string&& instead of std::string&& as parameter type in the first func overload.

1
Jan Schultke On

Basically, the addition of const to the first member of the pair means that you're passing an xvalue of type const std::string to func, and an rvalue reference std::string&& cannot bind to that, because it would discard the const qualifier.

Out of the two overloads, with const on the first member:

  • func(std::string&&) cannot be called
  • func(const std::string&) can be called, because const std::string& can bind to xvalues as well

Once you remove the const from the first member of the pair:

  • func(std::string&&) is a better match for an xvalue of type std::string, because it requires no qualification conversions
  • func(const std::string&) requires a qualification conversion (to add const), so it loses in overload resolution

Note that std::forward<T>(p).first does not turn into an lvalue, regardless whether first is const or not. This expression is always going to be an xvalue, assuming that p is an rvalue reference.