tower120
tower120

Reputation: 5255

C++11 templated function with rvalue param call

In some class O, I have templated function test2:

struct A{int value;};

struct O{
    A value;

    template<typename Args>
    static void test2(Args &&args){
        std::cout << std::endl << "!!!" << std::is_rvalue_reference<decltype(args)>::value << std::endl;
    }
};

Than, I want to call this function from another one:

template<typename Args>
void test(Args &&args){
    using t = decltype(std::forward<Args>(args).value);
    std::cout << std::is_rvalue_reference<decltype(args)>::value;
    std::cout << std::is_rvalue_reference<decltype(std::forward<Args>(args).value)>::value;
    std::cout << std::is_rvalue_reference<t>::value;

    // All ok
    O.test2(std::forward<Args>(args).value);            

    // Alvays rvalue, even if agrs is lvalue
    O::template test2<t>(
        std::forward<t>(
            std::forward<Args>(args).value
        )
    );

   // Nor work at all, cant cast A to A&&
   O::template test2<t>(
        std::forward<Args>(args).value
    );
);

}

http://coliru.stacked-crooked.com/a/3bbf040904845a54

If I just pass std::forward<Args>(args).value without specifying template type, it deduce type correctly, but what if I have to pass type, how should I call function than?

It seems I can't manually deduce type correctly.


UPDATE

I need to specify arguments explicitly, because I have function like this (pseudo-code):

//initially call wind from somewhere.

// Tuple defined in class and is std::tuple
template<class Callback, class ...Args>
void wind(Tuple&& tuple, Callback &&callback){   
    using elementT = decltype(std::get<index>(std::forward<Tuple>(tuple)));

    ///
    /// !!! Problem here !!!
    ///
    callback.template operator()<elementT, Args...>(  std::get<index>(std::forward<Tuple>(tuple))  );              // std::get automatically return &/&&

   // recursivly call wind until the end
   wind<Callback, Args...>( std::forward<Tuple>(tuple), std::forward<Callback>(callback));
}

// callback looks like:
struct CallMe{   
  // Args provide type info. No function arguments here.
  template<class Data, class ...Args>
  void operator(Data &&data){

  }
}

This question related to call both - wind and callback() functions.

Upvotes: 0

Views: 266

Answers (2)

tower120
tower120

Reputation: 5255

Based on knowledge that objects, inside rvalue referenced object, also rvalue referenced. I decide to ended up with this (inspired by https://stackoverflow.com/a/24083200/1559666):

template<class T, class FieldT>
using addRefU = typename std::conditional<
                            std::is_rvalue_reference<T>::value,
                            typename std::add_rvalue_reference< FieldT >::type,
                            typename std::conditional<
                                std::is_rvalue_reference<FieldT>::value,
                                typename std::add_rvalue_reference< FieldT >::type,
                                typename std::add_lvalue_reference< FieldT >::type
                            >::type
                        >::type;

    T           T::value(FieldT)    resolve
------------------------------------------------------------
 rvalue         lvalue              rvalue
 rvalue         rvalue              rvalue
 lvalue         rvalue              rvalue
 lvalue         lvalue              lvalue

using t = addRefU<decltype(args), decltype(args.value)>;
O::template test2<t>(
    static_cast<t>(args.value)
);

http://coliru.stacked-crooked.com/a/40d10f5a2f45c288

Short enough, as for me.

P.S. If someone have some precautions about this, I would gladly listen to them.

Upvotes: 0

Praetorian
Praetorian

Reputation: 109089

template<typename Args>
static void test2(Args&& args)
{ ... }

In the above function, even though the parameter type looks like an rvalue reference, it can bind to both rvalue and lvalue arguments. This is colloquially known as a universal reference. If the function argument is an rvalue of type U, then T is deduced as U, and T&& is U&&, an rvalue reference, which is straightforward. However, when the function argument is an lvalue of type U, then T will be deduced as U&, which means the function parameter type would be U& &&, which undergoes reference collapsing to become U&. However, for these special rules to apply, the type must be deduced.


O::test2(std::forward<Args>(args).value); // changed O.test2 to O::test2

In this case, the template parameter type is being deduced from the function argument. The argument itself is an lvalue, so the type of the function parameter args is also an lvalue.


O::template test2<t>(
    std::forward<t>(
        std::forward<Args>(args).value
    )
);

The difference here is that you've explicitly specified the template parameter type for test2. No deduction is taking place, and the function parameter is a simple rvalue reference in this case. It can only bind to rvalues, and you provide it this rvalue due to the outer std::forward<t> cast (here t = A). It has the same effect as static_cast<A&&>(value).


O::template test2<t>(
    std::forward<Args>(args).value
);

The last case should've explained why this doesn't work. As explained above, test2 can only bind to an rvalue in this case, and without the second std::forward from above, you're trying to bind an lvalue to an rvalue reference parameter, which fails.

Upvotes: 5

Related Questions