3

I wrote this code:

#include <iostream>

void test(std::string && x) {
    std::cout << "test: 1" << std::endl;
}
void test(const std::string & x) {
    std::cout << "test: 2" << std::endl;
}
template<typename T>
void test2(T&& x) {
    std::cout << "test2: 1" << std::endl;
}
template<typename T>
void test2(const T & x) {
    std::cout << "test2: 2" << std::endl;
}
int main()
{
    std::string x = "aaa";
    std::string & y = x;
    test(y);
    test2(y);
    return 0;
}

It has 4 functions, two named test and two test2. test2 have argument choosen by template and test has it static. Both types have one overload for const T & x and one for T && x. But if i call them with T &, different type is called. Result of code above is:

test: 2
test2: 1

How, and more importantly why, is this happening?

otto sleger
  • 141
  • 10
  • 1
    Similar: https://stackoverflow.com/questions/56548276/template-argument-deduction-for-references-as-arguments TL;DR: your `T` is deduced as `std::string`, not `std::string&`. – pptaszni Mar 28 '23 at 09:31
  • Even more interesting [example](https://stackoverflow.com/questions/9641960/c11-make-pair-with-specified-template-parameters-doesnt-compile) with `std::make_pair`. In this example however the type is deduced to `std::string&` because you can't bind lvalue to `&&` (standard quote: "If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction."). In your case `y` can be bound to `&&` because it's glvalue (please someone correct me if I'm wrong here). – pptaszni Mar 28 '23 at 09:51
  • 1
    @pptaszni: your TL;DR is backwards -- `T` is deduced as `std::string &` rather than `std::string` which is what makes it possible to match where `std::string &&` would not. The linked question has `T` rather than `T &&` as an argument, so it gets deduced the other way. – Chris Dodd May 06 '23 at 01:40

1 Answers1

0

Roughly speaking, the compiler prefers function overloads that involve "easier" type conversions.

Since you can't convert an lvalue reference to an rvalue reference, the function void test(std::string &&x); can't be called at all, but the conversion from std::string& to const std::string& is okay. That's the reason why you get "2" when calling test.

The template case is different because it also involves type deduction.

The T&& x is a "universal reference", which is able to bind to anything, and so it doesn't involve any type conversions. i.e. when T is deduced, the function test2(T &&x) becomes test2(std::string &x), which matches perfectly and requires no type conversions.

On the other hand test2(const T& x) becomes test2(const std::string &x), which requires 1 type conversion, so it's less favorable than the first overload.

muji4ok
  • 16
  • 1
    More specifically, when instantiating test2:1 for this call, it infers a type for `T` = `std::string &` (not `std::string`, which wouldn't match). You might think this would lead to an illegal type for the function like `test2(std::string & &&t)`, but since you can't have a reference to a reference that gets collapsed to `test2(std::string &t)` rather than being illegal. – Chris Dodd May 06 '23 at 01:35