-1

I am trying to implement a C++ matrix library (https://github.com/statslabs/matrix) according to Ch29 "A matrix design" of book "The C++ Programming Language". And find that I am not able to move exactly the same member function in Matrix and Matrix_ref back to Matrix_base.

The base class is a pure abstract class (https://github.com/statslabs/matrix/blob/master/include/slab/matrix/matrix_base.h):

template<typename T, size_t N>
class Matrix_base {
  public:
    // common stuff
    virtual T *data() = 0;
    virtual const T *data() const = 0;
  private:
    Matrix_slice<N> desc;  // the shape of the matrix
};

The first derived class is (https://github.com/statslabs/matrix/blob/master/include/slab/matrix/matrix.h):

template<typename T, size_t N>
class Matrix : public Matrix_base<T,N> {
    // special to Matrix
  public:
    T *data() { return elements.data(); }  // "flat" element access
    const T *data() const { return elements.data(); }
  private:
    vector<T> elements;
};

and the second derived class is (https://github.com/statslabs/matrix/blob/master/include/slab/matrix/matrix_ref.h):

template<typename T, size_t N>
class Matrix_ref : public Matrix_base<T,N> {
    // special to Matrix_ref
  public:
    T *data() { return ptr_; }  // "flat" element access
    const T *data() const { return ptr_; }
  private:
    T* ptr;  // the first element in the Matrix
};

The member functions in Matrix and Matrix_ref are exactly the same:

// m(i,j,k) subscripting with integers
template<typename T, std::size_t N>
template<typename... Args>
Enable_if<matrix_impl::Requesting_element<Args...>(), T &>
Matrix<T, N>::operator()(Args... args) {
  assert(matrix_impl::check_bounds(this->desc_, args...));
  return *(data() + this->desc_(args...));
}

template<typename T, std::size_t N>
template<typename... Args>
Enable_if<matrix_impl::Requesting_element<Args...>(), const T &>
Matrix<T, N>::operator()(Args... args) const {
  assert(matrix_impl::check_bounds(this->desc_, args...));
  return *(data() + this->desc_(args...));
}

where matrix_impl::Requesting_element<Args...>() make sure that the integers in Args... can be converted to std::size_t:

template<bool B, typename T = void>
using Enable_if = typename std::enable_if<B, T>::type;

template<typename X, typename Y>
constexpr bool Convertible() { return std::is_convertible<X, Y>::value; }

constexpr bool All() { return true; }

template<typename... Args>
constexpr bool All(bool b, Args... args) { return b && All(args...); }

template<typename... Args>
constexpr bool Requesting_element() {
  return All(Convertible<Args, size_t>()...);
}

Everything is okay when the mentioned member functions are in Matrix and Matrix_ref. However when I move all the codes to Matrix_base, it is seems that somehow the checking of Arg... no longer works properly by giving the following message:

/Users/pany/statslabs-project/matrix/examples/dgemm_example.cpp:33:7: error: no matching function for call to object of type 'slab::mat' (aka 'Matrix<double, 2>')
      A(i, j) = (double) (i * k + j + 1);
      ^
/Users/pany/statslabs-project/matrix/include/slab/matrix/traits.h:24:43: note: candidate template ignored: disabled by 'enable_if' [with Args = <int, int>]
using Enable_if = typename std::enable_if<B, T>::type;
                                      ^
/Users/pany/statslabs-project/matrix/include/slab/matrix/traits.h:24:43: note: candidate template ignored: disabled by 'enable_if' [with Args = <int, int>]

If you want to compile the project, you probably need to install both cmake and intel MKL (I want to implement some template functions as the interface to CBLAS). I thought it is simple refactoring of classes, but it just takes hours and I cannot figure out why it happens.

ypan1988
  • 71
  • 1
  • 5

1 Answers1

0

I find the solution and it seems to be a duplicated question (How to call a template member function in a template base class? ).

Now the codes has been moved to the MatrixBase:

template<typename T, std::size_t N>
template<typename... Args>
T &MatrixBase<T, N>::operator()(Args... args) {
  assert(matrix_impl::check_bounds(this->desc_, args...));
  return *(data() + this->desc_(args...));
}

template<typename T, std::size_t N>
template<typename... Args>
const T &MatrixBase<T, N>::operator()(Args... args) const {
  assert(matrix_impl::check_bounds(this->desc_, args...));
  return *(data() + this->desc_(args...));
}

And inside the derived classes Matrix and MatrixRef we have

// m(i,j,k) subscripting with integers
template<typename... Args>
Enable_if<matrix_impl::Requesting_element<Args...>(), T &>
operator()(Args... args) {
  return MatrixBase<T,N>::template operator()<Args...>(args...);
}

template<typename... Args>
Enable_if<matrix_impl::Requesting_element<Args...>(), const T &>
operator()(Args... args) const {
  return MatrixBase<T,N>::template operator()<Args...>(args...);
}

Note that the scripting checking will only be done in the derived classes since the base class is a pure abstract class and we will never create an object of MatrixBase.

ypan1988
  • 71
  • 1
  • 5