Home > Net >  C primer template universal reference and argument deduction
C primer template universal reference and argument deduction

Time:10-22

Hello I have this example from C primer:

 template <typename T>
 void f(T&& x) // binds to nonconstant rvalues
 {
     std::cout << "f(T&&)\n";
 }

 template <typename T>
 void f(T const& x) // lvalues and constant revalues
 {
     std::cout << "f(T const&)\n";
 }

And here is my attempt to test the output:

int main(){

    int i = 5;
    int const ci = 10;

    f(i); // f(T& &&) -> f(int&)
    f(ci); // f(T const&) -> f(int const&)
    f(5); // f(T &&) -> f(int&&)
    f(std::move(ci)); // f(T&&) -> f(int const&&)

    cout << '\n';
}

The output:

f(T&&)
f(T const&)
f(T&&)
f(T&&)
  • In the book the version of f taking a forwarding reference is said to bind only to non-constant rvalues but in main I've passed a constant rvalue f(5) and f(std::move(ci)) but still the first version called.

  • Is there a way to call the second version f(T const&) passing an rvalue? I know I can do it explicitly: f<int const&>(5); or f<int const&>( std::move(ci) ); but I want to know where can be called passing in an rvalue? Thank you!

  • I think the comment after the forwarding reference version of f is not correct: f(T&&) // binds to nonconstant rvalues because it can be bound to constant rvalues so for example:

      f(std::move(ci)); // f(int const&&) and not
      f(int const&)
    

CodePudding user response:

The comments in the book are, evidently, not entirely correct. When you have the two overloads available of

template <typename T> void f(T&& x);
template <typename T> void f(T const& x);

both of them can always be called with any argument (with some exceptions that I'll omit here), but the second one will be preferred if the argument is a const lvalue. The first one will be preferred under all other circumstances thanks to the reference collapsing rules that apply when deducing template arguments.

However, let's say that T is fixed by an enclosing class:

template <class T>
struct S {
    void f(T&& x);
    void f(T const& x);
};

and let's assume that T is a non-const object type. Now, the situation is different because T is not deduced when calling f. The comments in the book seem to be referring to this latter situation, where the first S::f now may be called with non-const rvalues of type T but not with const rvalues of type T nor lvalues of (possibly const) T. The code, unfortunately, doesn't match up with the comments.

Let's go back to the actual code that's in the book. As I said, the function taking T const& will be preferred when the argument is a const lvalue. Let's say that the argument is 5, but you want to explicitly call the second function even though it would not normally be selected. There are a few ways to do this. The one that is easiest to read is to actually turn the argument into a const lvalue:

f((const int&)5);

Your suggestion of f<const int&>(5) also works, but is a bit more confusing. The reason why it is confusing is that it requires the reader of the code to actually do the reference collapsing mentally and then remember that the first overload is less specialized than the second one. A third method is:

static_cast<void(*)(int const&)>(&f)(5);

Although this one is the hardest to read, the part before the (5) can be used whenever one particular overload needs to be extracted and bound to a function pointer.

  • Related