Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Universal reference with templated class

Example:

template <typename T>
class Bar
{
public:
    void foo(T&& arg)
    {
        std::forward<T>(arg);
    }
};

Bar<int> bar;    

bar.foo(10); // works

int a{ 10 };
bar.foo(a); // error C2664: cannot convert argument 1 from 'int' to 'int &&'

It seems that universal references works only with templated functions and only with type deduction, right? So it make no sense to use it with class? And does using of std::forward makes sense in my case?

like image 595
nikitablack Avatar asked Jun 01 '15 08:06

nikitablack


2 Answers

Note that the preferred terminology (i.e. the one which will be in future versions of the spec) is now forwarding reference.

As you say, a forwarding reference only works with type deduction in a function template. In your case, when you say T&&, T is int. It can't be int& because it has been explicitly stated in your Bar instantiation. As such, reference-collapsing rules can't occur, so you can't do perfect forwarding.

If you want to do perfect forwarding in a member function like that, you need to have a member function template:

template <typename U>
void foo(U&& arg)
{
    std::forward<U>(arg); //actually do something here
}

If you absolutely need U to have the same unqualified type as T, you can do a static_assert:

template <typename U>
void foo(U&& arg)
{
    static_assert(std::is_same<std::decay_t<U>,std::decay_t<T>>::value, 
                  "U must be the same as T");
    std::forward<U>(arg); //actually do something here
}

std::decay might be a bit too aggressive for you as it will decay array types to pointers. If that's not what you want, you could write your own simple trait:

template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename T, typename U>
using is_equiv = std::is_same<remove_cv_ref<T>, remove_cv_ref<U>>;

If you need a variadic version, we can write an are_equiv trait. First we need a trait to check if all traits in a pack are true. I'll use the bool_pack method:

namespace detail
{
    template<bool...> struct bool_pack;
    template<bool... bs>
    using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
}
template <typename... Ts>
using all_true = detail::all_true<Ts::value...>;

Then we need something to check if each pair of types in Ts... and Us... satisfy is_equiv. We can't take two parameter packs as template arguments, so I'll use std::tuple to separate them (you could use a sentinel node, or split the pack halfway through instead if you wanted):

template <typename TTuple, typename UTuple>
struct are_equiv;

template <typename... Ts, typename... Us>
struct are_equiv <std::tuple<Ts...>, std::tuple<Us...>> : all_true<is_equiv<Ts,Us>...>
{};

Then we can use this like:

static_assert(are_equiv<std::tuple<Ts...>,std::tuple<Us...>>::value, 
              "Us must be equivalent to Ts");
like image 185
TartanLlama Avatar answered Sep 28 '22 02:09

TartanLlama


You're right : "universal references" only appear when the type of a deduced parameter is T&&. In your case, there is no deduction (T is known from the class), hence no universal reference.

In your snippet, std::forward will always perform std::move as arg is a regular rvalue reference.

If you wish to generate a universal reference, you need to make foo a function template :

template <typename T>
class Bar
{
public:
    template <typename U>
    void foo(U&& arg)
    {
        std::forward<U>(arg);
    }
};
like image 39
Quentin Avatar answered Sep 28 '22 00:09

Quentin