Verschiedene Ausprägungen einer polymorphen Funktion

Bislang sahen alle Ausprägungen einer Template-Klasse oder einer Template-Funktion gleich aus. Sie unterschieden sich nur durch das Einsetzen unterschiedlicher Typparameter. Es ist aber auch möglich, unterschiedliche Varianten für einzelne Datentypen festzulegen.

Dies kommt uns zu Hilfe bei der Ausgabe einer Matrix. Die bisherige Fassung von print_matrix hoffte darauf, dass die jeweiligen Werte in double konvertierbar sind, was nicht ohne weiteres der Fall ist. Die Idee ist, dass die Ausgabe eines einzelnen Werts von dem der gesamten Matrix getrennt wird:

template<typename Matrix>
void print_matrix(const Matrix& A) {
   for (std::size_t i = 0; i < A.m; ++i) {
      std::printf("  ");
      for (std::size_t j = 0; j < A.n; ++j) {
         print_value(A(i, j));
      }
      std::printf("\n");
   }
}

Die neue Funktion print_value kann dann für verschiedene Varianten überladen werden:

void print_value(long double value) {
   std::printf(" %4.1Lf", value);
}

void print_value(double value) {
   std::printf(" %4.1lf", value);
}

void print_value(float value) {
   std::printf(" %4.1f", value);
}

Wenn dann jedoch eine Matrix von ganzen Zahlen angelegt wird, kommt es zu einem Fehler, weil für den Übersetzer alle angebotenen Varianten gleichrangig erscheinen (ganze Zahlen lassen sich in float, double oder long double konvertieren) und daher kein deterministisches Auswahlkriterium existiert:

int main() {
   Matrix<int> A(1, 1, StorageOrder::ColMajor);
   std::printf("A =\n"); print_matrix(A);
}
$shell> g++ -Wall -std=gnu++11 -o matrix_class15 matrix_class15.cpp
matrix_class15.cpp: In instantiation of 'void print_matrix(const Matrix&) [with Matrix = Matrix]':
matrix_class15.cpp:114:40:   required from here
matrix_class15.cpp:97:14: error: call of overloaded 'print_value(const int&)' is ambiguous
   print_value(A(i, j));
              ^
matrix_class15.cpp:80:6: note: candidate: void print_value(long double)
 void print_value(long double value) {
      ^
matrix_class15.cpp:84:6: note: candidate: void print_value(double)
 void print_value(double value) {
      ^
matrix_class15.cpp:88:6: note: candidate: void print_value(float)
 void print_value(float value) {
      ^

In solchen Fällen kann es eine Lösung sein, eine Template-Funktion des gleichen Namens hinzuzufügen. Diese wird aufgerufen, wenn keine der anderen Funktionen direkt anwendbar ist:

template<typename T>
void print_value(const T& value) {
   std::printf(" %4.1Lf", (long double) value);
}

Hier wird der Versuch unternommen, den Wert in long double zu konvertieren. Aber auch diese Variante wird nicht funktionieren, wenn Datentypen wie beispielsweise Complex verwendet werden.

Aufgabe

Die GMP-Bibliothek (GMP steht für GNU multiple precision library) stellt mehrere numerische Datentypen für höhere Genauigkeiten bereit. Unter anderem gibt es in dieser Bibliothek den Datentyp mpq_class für rationale Zahlen, d.h. der Nenner und der Zähler werden als getrennte, ganzzahlige Werte geführt. Hier ist ein Beispiel:

#include <gmpxx.h>
#include <cstdio>

int main() {
   mpq_class a(1, 3); /* one third */
   mpq_class b(2, 5); /* two fifth */
   mpq_class c = a + b;
   std::printf("%9s\n", c.get_str().c_str());
}
$shell> g++ -Wall -std=gnu++11 -o test_mpq test_mpq.cpp -lgmp
$shell> test_mpq
    11/15

Passen Sie die Vorlage so an, dass die print_value-Funktion auch rationale Zahlen unterstützt. Fügen Sie eine Template-Funktion scale_matrix hinzu, die eine Matrix mit einem Wert multipliziert. Nutzen Sie diese, um das alles mit rationalen Zahlen zu testen.

Vorlage

#include <cstddef> /* needed for std::size_t */
#include <cstdio> /* needed for printf */
#include <cassert> /* needed for assert */

enum class StorageOrder {ColMajor, RowMajor};

template<typename T>
struct Matrix {
   const std::size_t m; /* number of rows */
   const std::size_t n; /* number of columns */
   const std::size_t incRow;
   const std::size_t incCol;
   T* data;
   typedef T Element;

   Matrix(std::size_t m, std::size_t n, StorageOrder order) :
         m(m), n(n),
         incRow(order == StorageOrder::ColMajor? 1: n),
         incCol(order == StorageOrder::RowMajor? 1: m),
         data(new T[m*n]) {
   }

   ~Matrix() {
      delete[] data;
   }

   const T& operator()(std::size_t i, std::size_t j) const {
      assert(i < m && j < n);
      return data[i*incRow + j*incCol];
   }

   T& operator()(std::size_t i, std::size_t j) {
      assert(i < m && j < n);
      return data[i*incRow + j*incCol];
   }
};

template<typename T>
struct MatrixView {
   const std::size_t m; /* number of rows */
   const std::size_t n; /* number of columns */
   const std::size_t incRow;
   const std::size_t incCol;
   T* data;
   typedef T Element;

   MatrixView(std::size_t m, std::size_t n,
            T* data,
            std::size_t incRow, std::size_t incCol) :
         m(m), n(n),
         incRow(incRow), incCol(incCol),
         data(data) {
   }
   MatrixView(const MatrixView& other) :
      m(other.m), n(other.n),
      incRow(other.incRow), incCol(other.incCol),
      data(other.data) {
   }

   const T& operator()(std::size_t i, std::size_t j) const {
      assert(i < m && j < n);
      return data[i*incRow + j*incCol];
   }

   T& operator()(std::size_t i, std::size_t j) {
      assert(i < m && j < n);
      return data[i*incRow + j*incCol];
   }
};

template<typename Matrix>
void init_matrix(Matrix& A) {
   for (std::size_t i = 0; i < A.m; ++i) {
      for (std::size_t j = 0; j < A.n; ++j) {
         A(i, j) = j * A.n + i + 1;
      }
   }
}

void print_value(long double value) {
   std::printf(" %4.1Lf", value);
}

void print_value(double value) {
   std::printf(" %4.1lf", value);
}

void print_value(float value) {
   std::printf(" %4.1f", value);
}

template<typename Matrix>
void print_matrix(const Matrix& A) {
   for (std::size_t i = 0; i < A.m; ++i) {
      std::printf("  ");
      for (std::size_t j = 0; j < A.n; ++j) {
         print_value(A(i, j));
      }
      std::printf("\n");
   }
}

template<typename Matrix>
MatrixView<typename Matrix::Element> create_view(Matrix& A,
      std::size_t i, std::size_t j,
      std::size_t m, std::size_t n) {
   assert(i + m <= A.m && j + n <= A.n);
   return MatrixView<typename Matrix::Element>(m, n, &A(i, j),
      A.incRow, A.incCol);
}

int main() {
   Matrix<int> A(1, 1, StorageOrder::ColMajor);
   std::printf("A =\n"); print_matrix(A);
}