views:

95

answers:

1

The following small example shows my problem:

template<class T> struct X
{
    static void xxx(T& x) { }
    static void xxx(T&& x) { }
};

int main(int argc, char** argv)
{
    int x = 9;
    X<int>::xxx(x); // OK.
    X<int&>::xxx(x); // ERROR!
    return 0;
}

Error message (GCC):

error: ‘static void X::xxx(T&&) [with T = int&]’ cannot be overloaded
error: with ‘static void X::xxx(T&) [with T = int&]’

Why? T = int& ---> Is T& replaced by int&& in static void xxx(T& x)?

If the answer to the question is yes, then:

  • T& is not a lvalue-reference and it becomes a rvalue-reference!
  • And the following code should work:

But it didn't:

template<class T> struct X
{
    static void xxx(T& x) { }
};

int main(int argc, char** argv)
{
    X<int&>::xxx(2); // ERROR!
    return 0;
}

Error Message (GCC):

error: no matching function for call to ‘X::xxx(int)’
note: candidates are: static void X::xxx(T&) [with T = int&]

Then T& with T = int& is not equal to T&& and is not a rvalue-reference. but if it is not, why the first example not working? (it's a recursive problem!)


But the similar problem didn't occur for pointer types:

#include <iostream>

template<class T> struct X
{
    static void xxx(T* x) { std::cout << **x << std::endl; }
};

int main(int argc, char** argv)
{
    int x = 10;
    int* xx = &x;
    X<int*>::xxx(&xx); // OK. call X<int*>::xxx(int**)
    return 0;
}

Why references are different in this behavior?

+10  A: 

The C++0x FCD (N3092) has an explanation of how this works at §8.3.2/6 (reformatted for readability):

If a typedef, a type template-parameter, or a decltype-specifier denotes a type TR that is a reference to a type T,

  • an attempt to create the type "lvalue reference to cv TR" creates the type "lvalue reference to T"
  • an attempt to create the type "rvalue reference to cv TR" creates the type TR.

Let's consider your example (I've renamed your T to be TR so it matches the language above):

template<class TR> struct X
{
    static void xxx(TR& x)  { }
    static void xxx(TR&& x) { }
};

If we try instantiating X with TR = int& (so, T = int), the instantiations of xxx are as follows:

static void xxx(TR& x)  { }   -->   static void xxx(int& x) { }
static void xxx(TR&& x) { }   -->   static void xxx(int& x) { }

In the first case, we attempt to create an "lvalue reference to TR," which becomes an "lvalue reference to T." T is int, so the parameter type becomes int&.

In the second case, we attempt to create an "rvalue reference to TR," which becomes TR, which is int&.

The parameter type is the same for both overloads, hence the error.

James McNellis
I'm about 70% convinced you've memorized the specs completely
Michael Mrozek
@James McNellis: Thanks for expressing the meaning of TR.
PC2st
Is this the rule that makes `std::forward` work? That is, every C++0x programmer will have to know this... :)
UncleBens
@UncleBens: This rule wasn't put in for rvalue references, it dates back to before there were rvalue references. In C++98, "Attempting to create a reference to a reference type" causes type deduction to fail (§14.8.2/2). [CWG Defect 106](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#106) was raised and its resolution gives us the behavior I describe here (for lvalue references only). I had thought this rule was included in C++03 (Vandevoorde and Josuttis in _C++ Templates_ expected it to be), but it wasn't. This rule is required for `forward` to work, though.
James McNellis
This behavior is called _reference collapsing_ (I just remembered that; it's been driving me nuts all day that I couldn't remember what this was called).
James McNellis