Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ primer template universal reference and argument deduction

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&)
    
like image 859
Itachi Uchiwa Avatar asked Oct 21 '21 20:10

Itachi Uchiwa


People also ask

What is template argument deduction?

Template argument deduction is used when selecting user-defined conversion function template arguments. A is the type that is required as the result of the conversion. P is the return type of the conversion function template.

What is a universal reference?

Universal reference was a term Scott Meyers coined to describe the concept of taking an rvalue reference to a cv-unqualified template parameter, which can then be deduced as either a value or an lvalue reference.

What is template argument deduction in C++?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

Can a template parameter be deduced from a default argument?

Type template parameter cannot be deduced from the type of a function default argument: Deduction of template template parameter can use the type used in the template specialization used in the function call: Besides function calls and operator expressions, template argument deduction is used in the following situations:

What is the use of a template deduction?

Template argument deduction is used when selecting user-defined conversion function template arguments. A is the type that is required as the result of the conversion. P is the return type of the conversion function template, except that a) if the return type is a reference type then P is the referred type;

What is a universal reference in C++?

In practice, almost all universal references are parameters to function templates. Because the type deduction rules for auto -declared variables are essentially the same as for templates, it’s also possible to have auto -declared universal references.


Video Answer


1 Answers

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.

like image 163
Brian Bi Avatar answered Oct 19 '22 01:10

Brian Bi