Perfect forward return type but with const

Viewed 92

I want to perfect-forward a call to a function, but I always want to return a const version of the returned type. I mean, when returning a reference, I want a const& instead. How can I achieve that?

  • I don't care about non-reference types. After all, returning a const T (without a reference) is not much different than T.
  • I care only about a const at the top level. If T is a pointer type, or some other more complex type, I do not want to add constness somewhere deeper.

If I just forward the call normally:

template<class Func, class... Args>
decltype(auto) Example(Func f, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}

and f happens to return, say, int& then I just get just that: int&. But I would like to get a const int& instead.

decltype(auto) const, or decltype(auto const) seem to be illegal.

1 Answers

Here is a possible solution. References are transformed to const&. Any other type remains unchanged. However, I would like to hear a second opinion, if there are some corner cases I didn't consider.

#include <utility>

template<typename T>
struct add_const_to_ref {
        using type = T;
};

template<typename T>
struct add_const_to_ref<T&> {
        using type = const T&;
};

template<typename T>
using add_const_to_ref_t = typename add_const_to_ref<T>::type;

template<class Func, class... Args>
add_const_to_ref_t<decltype(std::declval<Func>()(std::forward<Args>(std::declval<Args&&>())...))> ConstInvoke(Func f, Args&&... args)
{
            return f(std::forward<Args>(args)...);
}

The following test:

#include <iostream>

int& foo(int& x) {
        return x;
}

int bar() {
        return 5;
}
int main() {
        int x = 0;
        std::cout << std::is_same_v<int&, decltype(ConstInvoke(foo, x))> << "\n";
        std::cout << std::is_same_v<int, decltype(ConstInvoke(foo, x))> << "\n";
        std::cout << std::is_same_v<const int&, decltype(ConstInvoke(foo, x))> << "\n";

        std::cout << std::is_same_v<int&, decltype(ConstInvoke(bar))> << "\n";
        std::cout << std::is_same_v<int, decltype(ConstInvoke(bar))> << "\n";
        std::cout << std::is_same_v<const int&, decltype(ConstInvoke(bar))> << "\n";
};

Gives the expected result:

0 0 1 0 1 0

i.e.

ConstInvoke(foo, x) returns const int&,

ConstInvoke(bar) returns int


An alternative, to simplify the horrible ConstInvoke, one can try like this:

template<typename T>
decltype(auto) as_const_to_ref(T&& arg) {
        return (add_const_to_ref_t<T>)(std::forward<T>(arg));
}

template<class Func, class... Args>
decltype(auto) ConstInvoke2(Func f, Args&&... args)
{
            return as_const_to_ref(f(std::forward<Args>(args)...));
}
Related