1

This overload resolution behavior baffles me:

#include "stdio.h" template<class T> class C { public: C(T v): m(v) {}; T m; template<class U> T f(U &&p) { printf("rRef called.\n"); return p; } template<class U> T f(const U &p) { printf("const Ref called.\n"); return p; } }; int main() { C<int> a(5); a.f<int&>(a.m); a.f(a.m); return 0; } 

Outputs:

const Ref called. rRef called. 

When debugging in gdb or Visual Studio, both debuggers show int C<int>::f<int &>() called in both cases, but the explicit template resolution resolves to the expected const ref, while the second resolves to a rvalue reference. Why? Why doesn't the compiler even try int C<int>::f<int>() which I thought would be the obvious match? How can a rvalue reference bind to a member value? Isn't a.m a lvalue?

1
  • 2
    U && is not an rvalue reference, but a forwarding reference in this instance, which can bind to anything. I'm more surprised by a.f<int&>(a.m) calling the const-ref version. Commented Oct 16, 2020 at 21:39

1 Answer 1

2

When you make the call:

a.f<int&>(a.m); 

the compiler has to choose between the following candidates:

template<class U> T f(U && p); // #1 template<class U> T f(U const & p); // #2 

For this overload resolution process, first both templates are transformed for the parameter int&.

Substituting U = int & for #1 gives int & &&, which due to reference collapsing, becomes int &.

Similarly, substituting U = int & for #2 gives int & const &, which again due to reference collapsing, becomes int &.

Now, since both overloads match, partial ordering is used to determine which template to call. Now U const & is more specialized than U &&. This is because U && can bind to all the types that U const & can, but the converse is not true.

Hence, since #2 is more specialized, it wins in overload resolution, and gets called.


In this call:

a.f(a.m); 

the template parameter is not specified. This means the parameter of #1 is considered to be a forwarding reference, and this matches all types that are passed in, and so #1 gets called.

Sign up to request clarification or add additional context in comments.

14 Comments

Hold on. "This matches exactly" - So would the overload with the forwarding reference, int & && becomes int &. Matching exactly doesn't seem to be the key here.
@StoryTeller-UnslanderMonica Yes, the forwarding reference matches, but is only chosen as a last resort, I thought.
Mmm, no, that's not how it works. It's a candidate like any other, always. This is likely due to partial ordering, not due to it being a forwarding reference directly.
@StoryTeller-UnslanderMonica Oh, so there's something more subtle about overload resolution I'm missing here then?
If U is explicitly specified U && is no longer a forwarding reference, but a regular rvalue reference as far as I know.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.