Constructors

So far, we started to initialized a matrix outside the struct:

Matrix A;
A.m = 7; A.n = 8;
A.data = new double[A.m * A.n];
A.incRow = 1; A.incCol = 7;

This is not very readable and invites mistakes if many matrices are initialized this way. Fortunately, the construction of an object can be done in a so-called constructor which is a special method used during the construction of an object. Constructors look like methods but they are named after the struct (or the class) and they have no return type:

struct Matrix {
   std::size_t m; /* number of rows */
   std::size_t n; /* number of columns */
   std::ptrdiff_t incRow;
   std::ptrdiff_t incCol;
   double* data;

   Matrix(std::size_t m_, std::size_t n_,
         std::ptrdiff_t incRow_, std::ptrdiff_t incCol_) {
      m = m_; n = n_; incRow = incRow_; incCol = incC_;
      data = new double[m*n];
   }

   /* ... */
};

The constructor is then implicitly invoked when the declaration of a variable of this type is executed. The parameters given at the declaration are passed to the constructor method.

Matrix A(7, 8, 1, 7);

When a constructor is provided for a class, it has to be used. (You can have multiple constructors but then you need to chose one of them.) In this case, Matrix can no longer be declared without the four parameters required for the constructor. As some components of a struct or class possibly have constructors asking for parameters, not all initializations can be done within the body of a constructor. Instead it is possible within a C++ constructor to use a so-called initializer which is syntactically located between the parameter list and the opening brace of the compound statement using a leading colon:

struct Matrix {
   std::size_t m; /* number of rows */
   std::size_t n; /* number of columns */
   std::ptrdiff_t incRow;
   std::ptrdiff_t incCol;
   double* data;
   Matrix(std::size_t m, std::size_t n,
         std::ptrdiff_t incRow, std::ptrdiff_t incCol) :
         m(m), n(n), incRow(incRow), incCol(incCol),
         data(new double[m*n]) {
   }

   /* ... */
};

The individual to be initialized components of the struct or class are to be constructed in the order of their appearance. Elementary types like std::size_t or double* are treated like objects in C++ which offer a constructor with one parameter of the same type (a so-called copy constructor).

Since C++11 it is possible to use parameter names for the constructor that are identical to the corresponding components of the struct or class. Whenever a name is looked up within an initializer of a constructor, the parameter names are considered first. This allows us to simplify constructors as we do no longer have to invent names like m_, n_ etc.

In addition, such initializers allow us to initialize variables that are declared constant and which cannot be changed within the body of the constructor. As variables like m and n do not change during the lifetime of a matrix, this can be made explicit by declaring them const:

struct Matrix {
   const std::size_t m; /* number of rows */
   const std::size_t n; /* number of columns */
   const std::ptrdiff_t incRow;
   const std::ptrdiff_t incCol;
   double* data;
   Matrix(std::size_t m, std::size_t n,
         std::ptrdiff_t incRow, std::ptrdiff_t incCol) :
         m(m), n(n), incRow(incRow), incCol(incCol),
         data(new double[m*n]) {
   }

   /* ... */
};

Exercise

As we work in case of dense matrices just with two possible organizations (row major and column major), it appears straightforward just to specify one of these two variants instead of specifying incRow and incCol explicitly. This can be done by defining an enumeration type StorageOrder:

enum class StorageOrder {ColMajor, RowMajor};

Values of type StorageOrder can have the two possible values StorageOrder::ColMajor and StorageOrder::RowMajor.

Adapt the constructor in the following example. You might want to consider the ?: operator in C++. The expression condition? expr1: expr2 evaluates expr1 if condition evaluates to true, otherwise expr2 is evaluated.

Example

#include <cstddef> /* needed for std::size_t and std::ptrdiff_t */
#include <printf.hpp> /* needed for fmt::printf */

struct Matrix {
   const std::size_t m; /* number of rows */
   const std::size_t n; /* number of columns */
   const std::ptrdiff_t incRow;
   const std::ptrdiff_t incCol;
   double* data;

   Matrix(std::size_t m, std::size_t n,
         std::ptrdiff_t incRow, std::ptrdiff_t incCol) :
         m(m), n(n), incRow(incRow), incCol(incCol),
         data(new double[m*n]) {
   }

   void init() {
      for (std::size_t i = 0; i < m; ++i) {
         for (std::size_t j = 0; j < n; ++j) {
            data[i*incRow + j*incCol] = j * m + i + 1;
         }
      }
   }

   void print() {
      for (std::size_t i = 0; i < m; ++i) {
         fmt::printf("  ");
         for (std::size_t j = 0; j < n; ++j) {
            fmt::printf(" %4.1lf", data[i*incRow + j*incCol]);
         }
         fmt::printf("\n");
      }
   }
};

int main() {
   Matrix A(7, 8, 1, 7);
   A.init();
   fmt::printf("A =\n"); A.print();
   delete[] A.data;
}