Reputation: 1755
I wrote some code that manages to mix class templates, class inheritance, and operator overloading and I'm lost as to how to solve an issue regarding operator use. I have a base class with the bulk of the code (particularly, operator overloading implementation and data holders):
template <typename _type>
class baseMatrix {
public:
baseMatrix();
~baseMatrix();
//operators
baseMatrix<_type>& operator= (baseMatrix<_type> _mat);
template <typename _input_type> baseMatrix<_type>& operator*= (_input_type _val);
template <typename _input_type> baseMatrix<_type>& operator/= (_input_type _val);
template <typename _input_type> baseMatrix<_type>& operator+= (_input_type _val);
template <typename _input_type> baseMatrix<_type>& operator-= (_input_type _val);
template <typename _input_type> baseMatrix<_type>& operator*= (const baseMatrix<_input_type>& _mat);
template <typename _input_type> baseMatrix<_type>& operator/= (const baseMatrix<_input_type>& _mat);
template <typename _input_type> baseMatrix<_type>& operator+= (const baseMatrix<_input_type>& _mat);
template <typename _input_type> baseMatrix<_type>& operator-= (const baseMatrix<_input_type>& _mat);
protected:
std::vector<_type> data;
};
/* ... */
template <typename _type>
template <typename _input_type>
baseMatrix<_type>& baseMatrix<_type>::operator*=(_input_type _val) {
for (int i = 0; i < data.size(); ++i) data[i]*=_val;
return *this;
};
template <typename _type>
template <typename _input_type>
baseMatrix<_type>& baseMatrix<_type>::operator*=(const baseMatrix<_input_type>& _mat) {
for (int i = 0; i < data.size(); ++i) data[i]*=_mat.data[i];
return *this;
};
/* remaining operator overload functions */
I overloaded operators for both scalar and class arguments. I then have and additional class matrix2D
that inherits those operators from baseMatrix
:
template <typename _type>
class matrix2D : public baseMatrix<_type> {
public:
matrix2D(int _rows, int _cols);
matrix2D(int _rows, int _cols, _type _val);
~matrix2D();
_type& operator()(int _r, int _c);
_type& at(int _r, int _c);
protected:
int nRows,nCols;
using baseMatrix<_type>::data;
};
However, when instantiating these classes I'm only able to call the scalar operators, as using e.g. *=
with two matrix2D
objects results in a compilation error:
In file included from test.cpp:1:
baseMatrix.hpp: In instantiation of ‘baseMatrix<_type>& baseMatrix<_type>::operator*=(_input_type) [with _input_type = matrix2D<float>; _type = float]’:
test.cpp:29:6: required from here
baseMatrix.hpp:56:47: error: no match for ‘operator*=’ (operand types are ‘__gnu_cxx::__alloc_traits<std::allocator<float>, float>::value_type’ {aka ‘float’} and ‘matrix2D<float>’)
for (int i = 0; i < data.size(); ++i) data[i]*=_val;
If, on the other hand, I instantiate a baseMatrix
object, it compiles OK (fails at runtime for other reasons, i.e. unitialized data):
int main(int argc, char const *argv[]){
matrix2D<float> M1(5,5,0.0);
matrix2D<float> M2(3,3,6.0);
baseMatrix<float> testM;
M2*=0.47; // works
M2*=M1; // does not compile
M2*=testM // runtime error (segfault)
}
So apparently the operator overload is not working for derived classes, what would be the correct syntax?
EDIT: I've realized that the problem is with multiple operator overloads. For some reason it is able to compile if I only declare the operators to take a baseMatrix
object as argument, or vice-versa.
Upvotes: 0
Views: 193
Reputation: 72473
So for M1*=M2
, overload resolution checks to see whether operator*=(M1, M2)
and/or M1.operator*=(M2)
are possible. There are no good candidates for non-member operator*=
. Since M1
has type matrix2D<float>
which inherits baseMatrix<float>
, the compiler sees that M1
has member functions:
template <typename _input_type> baseMatrix<float>& operator*= (_input_type _val); // #1
template <typename _input_type> baseMatrix<float>& operator*= (const baseMatrix<_input_type>& _mat); // #2
The next step is to attempt to deduce template arguments for each template in the overload set. Both succeed: For template #1, it finds a valid specialization by just taking _input_type = matrix2D<float>
:
baseMatrix<float>& baseMatrix<float>::operator*=<matrix2D<float>>(matrix2D<float> _val); // #3
For template #2, it finds a specialization by using the base class of the M2
argument type and determining _input_type = float
:
baseMatrix<float>& baseMatrix<float>::operator*=<float>(const baseMatrix<float>& _val); // #4
Then these function template specializations are compared. The trouble here is that you meant to use #4, but #3 is considered a better match than #4, since #3 uses the exact type of argument M2
, but #4 needs a derived-to-base conversion. Then the instantiation of #3 contains the statement data[i]*=_val;
which doesn't make sense for data[i]
a float
and _val
a matrix2D<float>
, causing the error.
One solution is to use a SFINAE technique to make sure template #1 can't be used with a matrix type:
#include <utility>
#include <type_traits>
template <typename Type>
class baseMatrix {
private:
template <typename ValType>
static constexpr std::true_type is_matrix_ptr(const baseMatrix<ValType>*);
static constexpr std::false_type is_matrix_ptr(const void*);
public:
// ...
template <typename ScalarType,
std::enable_if_t<!decltype(is_matrix_ptr(std::declval<ScalarType*>()))
::value>* = nullptr>
baseMatrix& operator*=(ScalarType val);
// ...
};
Upvotes: 1