Search code examples
c++templatesoperator-overloadingfriendprotected

C++ Access Protected Member of Templated Struct with Different Template Parameter Values


I am creating a templated matrix multiplication function where the returned matrix is a different size than the left-hand-side matrix. In that function I need to access a protected member of the matrix to be returned, but I get compiler error C2248: cannot access protected member. I believe this occurs because the template arguments are different sizes.

template<typename T, std::size_t r, std::size_t c>
struct Matrix
{
  template<std::size_t c2>
  Matrix<T, r, c2> operator*(const Matrix<T, c, c2>& rhs);
protected:
  int test;
};

template<typename T, std::size_t r, std::size_t c>
template<std::size_t c2>
Matrix<T, r, c2> Matrix<T, r, c>::operator*(const Matrix<T, c, c2>& rhs)
{
  Matrix<T, r, c2> retMat;
  retMat.test;
  return retMat;
}

I tried friending the operator*() multiplication function:

...
protected:
  int test;
  template<std::size_t c2>
  friend Matrix<T, r, c> Matrix<T, r, c2>::operator*(const Matrix<T, c2, c>& rhs);
};

but I get:

error C2245: non-existent member function Matrix<T,r,c>::operator * specified as friend (member function signature does not match any overload)

How can I access the protected variable in a Matrix with different sized template parameter arguments?


Solution

  • cannot access protected member. I believe this occurs because the template arguments are different sizes.

    Exactly: Matrix<int, 2, 3> is a different type from (by example) Matrix<int, 3, 4>; so inside a method of Matrix<int, 2, 3> you can't access the test member of Matrix<int, 3, 4>.

    I tried friending the operator*() multiplication function

    It's the right way but you forgot an important element: a friend function isn't a method of the class; it's a normal function.

    If you implement operator*() as method of a class (the wrong way for many reasons), it needs only an explicit parameter (the operand on the right of *) because the other element (the operand on the left of *) is, implicitly, the *this element.

    But when you implement operator*() as a function (friend to the class or not), there isn't the implicit *this element; so the function needs two parameters.

    So is wrong your

    template<std::size_t c2>
    friend Matrix<T, r, c> Matrix<T, r, c2>::operator*(const Matrix<T, c2, c>& rhs);
    

    because receive only a parameter and isn't a Matrix<T, r, c2>::.

    The correct way to solve this problem (IMHO) is a function (not method) as follows

    template <typename T, std::size_t D1, std::size_t D2, std::size_t D3>
    Matrix<T, D1, D3> operator* (Matrix<T, D1, D2> const & m1,
                                 Matrix<T, D2, D3> const & m2)
     {
       Matrix<T, D1, D3> retMat;
       retMat.test = m1.test + m2.test;
       return retMat;
     }
    

    that has to be friend to Matrix<T, D1, D2>, Matrix<T, D2, D3> and Matrix<T, D1, D3>.

    You can be temped to define it inside the class

    template <typename T, std::size_t R, std::size_t C>
    struct Matrix
     {
       protected:
          T test;
    
          template <typename U, std::size_t D1, std::size_t D2, std::size_t D3>
          friend Matrix<U, D1, D3> operator* (Matrix<U, D1, D2> const & m1,
                                              Matrix<U, D2, D3> const & m2)
           {
             Matrix<T, D1, D3> retMat;
             retMat.test = m1.test + m2.test;
             return retMat;
           }
     };
    

    but this is wrong because cause a multiple definition of the same function in different classes.

    So I suggest of declare (but not define) the operator*() friend inside the class

    template <typename T, std::size_t R, std::size_t C>
    struct Matrix
     {
       protected:
          T test;
    
          template <typename U, std::size_t D1, std::size_t D2, std::size_t D3>
          friend Matrix<U, D1, D3> operator* (Matrix<U, D1, D2> const & m1,
                                              Matrix<U, D2, D3> const & m2);
     };
    

    and define it outside.

    The following if a full working example

    #include <type_traits>
    
    template <typename T, std::size_t R, std::size_t C>
    struct Matrix
     {
       protected:
          T test;
    
          template <typename U, std::size_t D1, std::size_t D2, std::size_t D3>
          friend Matrix<U, D1, D3> operator* (Matrix<U, D1, D2> const & m1,
                                              Matrix<U, D2, D3> const & m2);
     };
    
    
    template <typename T, std::size_t D1, std::size_t D2, std::size_t D3>
    Matrix<T, D1, D3> operator* (Matrix<T, D1, D2> const & m1,
                                 Matrix<T, D2, D3> const & m2)
     {
       Matrix<T, D1, D3> retMat;
       retMat.test = m1.test + m2.test;
       return retMat;
     }
    
    int main ()
     {
       Matrix<long, 2U, 3U> a;
       Matrix<long, 3U, 4U> b;
    
       auto  c { a * b };
    
       static_assert( std::is_same<decltype(c), Matrix<long, 2U, 4U>>{}, "!" );
     }