std::remove_reference explained?

Koushik Shetty picture Koushik Shetty · Jun 8, 2013 · Viewed 14.6k times · Source

I saw possible implementations for std::remove_reference as below

template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};   

Why is it that there are specializations for lvalue and rvalue reference? Won't the general template itself be sufficient and remove the reference? I'm confused here because in the T& or T&& specialization if I try to use ::type I should still get T& or T&& respectively right?

Could you explain how, why we cast to remove_reference<t>::type&& in move? (is it because that the parameter is named so it will be treated as an lvalue inside the move function?).

Also, could you point out a way whereby I can find out and print what the type is? for e.g if its an rvalue of type int then I should be able to print out that int&& was passed? (I've been using std::is_same to check but manually.)

Thank you for your time.

Answer

Andy Prowl picture Andy Prowl · Jun 8, 2013

why is it that there are specializations for lvalue and rvalue reference?

If only the primary template existed, then doing:

remove_reference<int&>::type

Would give you:

int&

And doing:

remove_reference<int&&>::type

Would give you:

int&&

Which is not what you want. The specializations for lvalue references and rvalue references allow stripping the & and the &&, respectively, from the type argument you pass.

For instance, if you are doing:

remove_reference<int&&>

The type int&& will match the pattern specified by the T&& specialization, with T being int. Since the specialization defines the type alias type to be T (in this case, int), doing:

remove_reference<int&&>::type

Will give you int.

could you explain how, why we cast to remove_reference<t>::type&& in move?

That's because if move() were defined as follows:

    template<typename T>
    T&& move(T&& t) { ... }
//  ^^^
//  Resolves to X& if T is X& (which is the case if the input has type X
//  and is an lvalue)

Then the return type will be X& if the argument of move() is an lvalue of type X (that's how so-called "universal references"). We want to make sure that the return type is always an rvalue reference.

The purpose of move() is to give you back an rvalue, no matter what you pass in input. Since a function call for a function whose return type is an rvalue reference is an rvalue, we really want move() to always return an rvalue reference.

That's why we do remove_reference<T>::type&&, because appending && to a non-reference type is always guaranteed to yield an rvalue reference type.

Also could you point out a way whereby I can find out and print what the type is?

I'm not sure what you mean by "print" here. There is no portable way I know of converting the name of a type to a string (no matter how you obtain that type).

If your goal is to make sure that an rvalue was passed, on the other hand, you could use a static assertion like so:

#include <type_traits>

template<typename T>
void foo(T&&)
{
    static_assert(!std::is_reference<T>::value, "Error: lvalue was passed!");
    // ...
}

Which relies on the fact that when an lvalue of type X is being passed, T will be deduced to be X&.

You could also use an equivalent SFINAE-constraint, if you only want to produce a substitution failure:

#include <type_traits>

template<typename T, typename std::enable_if<
    !std::is_reference<T>::value>::type* = nullptr>
void foo(T&&)
{
    // ...
}