Search code examples
c++c++11template-argument-deductionperfect-forwardingreference-collapsing

Template argument deduction in perfect forwarding


I'm learning perfect forwarding, and run the example code(listed bellow)

template<typename T>
void show_type(T t){
    std::cout << typeid(t).name() << std::endl;
}

template<typename T>
void perfect_forwarding(T &&t){
    std::cout << std::boolalpha << "is std::string: " << std::is_same<std::string, T>::value << std::endl;
    std::cout << std::boolalpha << "is lval-reference: " << std::is_lvalue_reference<T>::value << std::endl;
    std::cout << std::boolalpha << "is rval-reference: " << std::is_rvalue_reference<T>::value << std::endl;
    show_type(static_cast<T&&>(t));
}

std::string get_string(){
  return "hi world";
}

int main() {
  std::string s = "hello world";
  perfect_forwarding(s);              //call 1
  perfect_forwarding(get_string());   //call 2
}

The output is

is std::string: false
is lval-reference: true
is rval-reference: false
Ss
is std::string: true
is lval-reference: false
is rval-reference: false
Ss

And I debug with gdb, find out the two calls to perfect_forwarding() are instantiated as

perfect_forwarding<std::string&>(std::string&) //for call 1
perfect_forwarding<std::string>(std::string&&) //for call 2

My question is

In call 1, why T was deducted as std::string&, I thought it would be std::string, and T&& should be deducted as std::string&&

In call 2, I thought T should be deducted as std::string&&

I can't find an article talk about this


Solution

  • When you pass an argument to a function which has a forwarding reference T&& as a parameter for that argument, one of two things happen:

    • If the argument is an lvalue (such as s), T deduces to an lvalue reference (such as std::string&). There are no references to references, so you get reference collapsing. For instance, T&& where T = std::string& appears to be an rvalue reference to an lvalue reference, and this collapses to just std::string&.

    • Otherwise, if the argument is an rvalue (such as get_string()), T deduces to the type of the expression (std::string), so T&& is simply std::string&&.

    See also template deduction rules for a more detailed explanation.

    Note that in your GDB inspection of

    perfect_forwarding<std::string&>(std::string&)
    perfect_forwarding<std::string>(std::string&&)
    

    ... what appears between pointy brackets is what T deduced to, and what appears between parentheses is what T&& collapsed to.