Simon Segert
Simon Segert

Reputation: 431

Perfect forwarding with template classes

Suppose I have a template class, for example template<int n, int m> class Matrix

Is there a way to define a matrix multiplication operator * so that

  1. the arguments to * can be either lvalues or rvalue references
  2. *infers the appropriate return type (i.e. the appropriate template parameters) from its arguments

What I have in mind is something like

template< int n,int k, int m, template<int,int> class T1, template<int, int> class T2, template<int,int>class T3 >
 T3<n,m> operator*(T1<n,k>&&, T2<k,m>&&)//does not work

When I try to run the above code (with the bodies filled in in the obvious ways), I get an error:

Cannot convert from Matrix<1,1> to Matrix<1,1>&&

when the arguments are lvalues.

Upvotes: 0

Views: 811

Answers (2)

oisyn
oisyn

Reputation: 1376

I would simply stick to const references as well, as explained by previous answer. But to clarify why your code doesn't work, perfect forwarding only applies when you use an rvalue reference to an cv-unqualified template parameter. In layman's terms, it has to be just T&&, where T is a function template parameter:

template<class T>
void ForwardMe(T&& t)
{
    DoSomething(std::forward<T>(t));
}

The idea is that the compiler will be able to deduce T as type& when passed a lvalue (so the function signature becomes void ForwardMe(type&) because of reference collapsing rules), or just type in case of rvalues (signature becomes void ForwardMe(type&&)).

In your example, you do something like:

template<int N, template<int> class T>
void ForwardMe(T<N>&& t)
{
    // ...
}

This doesn't work as you had expected, because the compiler cannot deduce T to be a reference to something, so you can't have perfect forwarding. The function parameter t will therefore only match rvalue references.

Since const references can bind to temporaries, using const T<N>& instead in above example will solve your problems. But if you really want to support both lvalue and rvalue inputs (because you like to have move semantics where appropriate), you have two options:

  • Write overloads for all 4 permutations: lvalue*lvalue, lvalue*rvalue, rvalue*lvalue, rvalue*rvalue.
  • Write a generic function template and use SFINAE to limit the input types.

The latter would be something like:

#include <type_traits>

template<class L, class R>
struct MatrixMulResult_helper;
template<int n, int m, int k, template<int, int> class T>
struct MatrixMulResult_helper<T<n, m>, T<m, k>> { using type = T<n, k>; };

template<class L, class R>
using MatrixMulResult = typename MatrixMulResult_helper<L, R>::type;

template<class L, class R>
MatrixMulResult<std::decay_t<L>, std::decay_t<R>>
operator*(L&& lhs, R&& rhs)
{
    // ...
}

The compiler is now free to deduce L and R as references. MatrixMulResult<> makes sure that this function is only defined when (the decayed types of) L and R are respectively of the form T<n,m> and T<m,k>. It returns a T<n,k>.

Upvotes: 2

user673679
user673679

Reputation: 1366

Yes. From my own code:

template
<
    int LeftColumnsRightRows, int LeftRows,
    int RightColumns
>
Matrix<RightColumns, LeftRows> operator*(Matrix<LeftColumnsRightRows, LeftRows> const& a, Matrix<RightColumns, LeftColumnsRightRows> const& b)

And I don't know why you'd want it to take &&s. If you want to convert two other types to matrices and then multiply them, you should do the conversion outside of the multiplication operator.

Upvotes: 2

Related Questions