56

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.

kvantour
  • 25,269
  • 4
  • 47
  • 72
Koushik Shetty
  • 2,146
  • 4
  • 20
  • 31

2 Answers2

54

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&&)
{
    // ...
}
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • then why is the specialization for `T&&`? general template can handle this right? – Koushik Shetty Jun 08 '13 at 14:01
  • @Koushik: You mean the primary template? Well, if only the primary template existed, then `remove_reference::type` would give you back `int&&` – Andy Prowl Jun 08 '13 at 14:02
  • yes correction made. in the specializations also we get the same right? for `T&&` we get `T&&` for `::type` right? – Koushik Shetty Jun 08 '13 at 14:04
  • 1
    @Koushik: No, we get `T`, because the specialization matches the pattern `T&&`, so if you pass (say) `int&&` and match it with `T&&`, your `T` must be just `int` – Andy Prowl Jun 08 '13 at 14:05
  • oh ok ok ok...:-) i'm so dumb. now everything is crystal clear. i was under the impressin that for `T&&` specialization the `T` in the typedef is still `T&&`. but i'm wrong.it is `T` . Thanks a lot:-) – Koushik Shetty Jun 08 '13 at 14:08
  • @Koushik: You're not dumb, it's just a matter of practice ;) Getting the hang of these things is not that simple – Andy Prowl Jun 08 '13 at 14:10
  • ah yes i need to practice a lot. These type of specializations are applicable to template functions also? – Koushik Shetty Jun 08 '13 at 14:13
  • @Koushik: Yes and no. Explicit specializations are allowed, partial specializations are not. And even when it is allowed, specializing function templates is usually considered a bad idea, because it does not always do what you think. Functions can be *overloaded* on the other hand, and function overloaded is considered to be a superior technique. It can solve most of the problems (sometimes using a technique called "tag dispatch"), so the situations that *really* call for function template specialization are rare. I would advise using overloading as much as you can for function templates – Andy Prowl Jun 08 '13 at 14:18
  • ah the SFINAE constraint looks more juicy. i think it will be better to use that and yes i will look at overloads henceforth. Thanks for all your help.:-) – Koushik Shetty Jun 08 '13 at 14:21
  • It is actually not standard compliant to have a void pointer as a non type template parameter. You should use an anonymous `typename = std::enable_if_t<...>` instead – Curious Jul 02 '16 at 04:02
  • Won't `std::is_reference::value` be true for rvalues too? – Baruch Jan 08 '18 at 14:44
  • @baruch Search for "Forwarding References" on Google. It is a special case in order to deal with the difference between `lvalue` and `rvalue`. – wangkaibule Sep 20 '18 at 14:03
  • And there is a good post on this topic: [Perfect Forwarding](https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c) – wangkaibule Sep 20 '18 at 14:41
0

When you treat some type as template parameter, compiler searches for the most "specialized" specialization. If you pass a int&& to this template, compiler use remove_reference<T&&> version. General specialization don't give you what you want - if you pass int&& to general specialiation, type will be int&&

If you want to print type, use typeid(some_type).name()

Evgeny Eltishev
  • 593
  • 3
  • 18
  • i used typeid but in gcc it was something awkward. i think i have to use some filter right? other than typeid is there any compile time stuff? so that i can use with static_assert? – Koushik Shetty Jun 08 '13 at 14:10
  • @Koushik: Could you please clarify that part of the question? Do you want to get a string which contains the name of the type, or do you just want to make sure an rvalue reference was passed? – Andy Prowl Jun 08 '13 at 14:11
  • @AndyProwl it will be good to get the string during debug phase and yes i want to make sure only rvalue was passed andy. if i can get this during compile time then its most helpful. – Koushik Shetty Jun 08 '13 at 14:16
  • @Koushik: OK, I edited my answer. There is no portable way of getting the name of a type, though (to some extent this is possible though: you can google for "C++ pretty print") – Andy Prowl Jun 08 '13 at 14:19
  • You can run `c++filt -t` to unmangle gcc's `typeid()::name()` output. For example, `c++filt -t FRSt8ios_baseS0_E` returns `std::ios_base& (std::ios_base&)` - which is the type of the function `std::boolalpha`. – BingsF Jun 16 '16 at 07:12