Remove reference in decltype (return T instead of T& where T& is the decltype)

leemes picture leemes · Nov 2, 2012 · Viewed 21.3k times · Source

(If you're a C++11 pro, skip to the bold paragraph.)

Let's say I want to write a template method which calls and returns the result of a passed object which type is the template parameter:

template<ReturnType, T>
ReturnType doSomething(const T & foo) {
    return foo.bar(); // EDIT: Might also be an expression introducing a temp val
}

So T has to have a method ReturnType T::bar() const in order to be used in a call like this:

struct MyClass {
    ...
    int bar() const;
    ...
};
...
MyClass object;
int x = doSomething<int, MyClass>(object);

We don't have to write MyClass thanks to type deduction and the call becomes:

int x = doSomething<int>(object);

But omitting <int> too results in a compilation error because the method doesn't require to return int in order to be assigned to x afterwards (it could return char for example).

In C++0x/11 we have the auto and decltype with which we can use to deduce the return type of a template method:

template<T>
auto doSomething(const T & foo) -> decltype(foo.bar()) {
    return foo.bar(); // EDIT: Might also be an expression introducing a temp val
}

The compiler will now find out what the type of foo.bar() is and just uses this as the return type. With our concrete class MyClass this will be an int and the following would suffice:

int x = doSomething(object);

Now to my question:

If MyClass defines bar() as returning an int&, the return type of doSomething(object) will also be an int& = decltype(foo.bar()). This is a problem, since as G++ now complies that I'm returning reference to temporary.

How can I fix this? Is there something like remove_reference which can be used like remove_reference(decltype(foo.bar()))?

I thought about just declaring a helper method which takes a T& and returns a T and then define the return type of doSomething to be decltype(helper(foo.bar())). But there has to be a better way, I'm feeling it.

Answer

GManNickG picture GManNickG · Nov 2, 2012

To remove a reference:

#include <type_traits>

static_assert(std::is_same<int, std::remove_reference<int&>::type>::value, "wat");

In your case:

template <typename T>
auto doSomething(const T& foo)
    -> typename std::remove_reference<decltype(foo.bar())>::type
{
    return foo.bar();
}

Just to be clear, note that as written returning a reference is just fine:

#include <type_traits>

struct f
{
    int& bar() const
    {
        static int i = 0;
        return i;
    } 
};

template <typename T>
auto doSomething(const T& foo)
    -> decltype(foo.bar())
{ 
    return foo.bar();
}

int main()
{
    f x;
    return doSomething(x);
}

The returned reference can simply be passed on without error. Your example in the comment is where it becomes important and useful:

template <typename T>
auto doSomething(const T& foo)
    -> decltype(foo.bar())
{ 
    return foo.bar() + 1; // oops
}