Как создать базовый класс при использовании шаблона CURLYURURRING Template? - PullRequest
0 голосов
/ 25 ноября 2018

Я строю свою собственную реализацию Array с поддержкой нескольких новых функций и операторов.Я много исследовал о расширении std::array и, в конце концов, это вызывает столько проблем, и я решил использовать композицию вместо наследования.

Ниже мы видим небольшую частьмоей Array пользовательской реализации с шаблонным метапрограммированием.В этой простой версии есть метод печати для std::ostream и простое определение operator/:

#include <array>
#include <iostream>

template <unsigned int array_width, typename DataType, typename DerivedType>
struct Array {
  std::array<DataType, array_width> _data;

  Array() {
    for(int index = 0; index < array_width; ++index) _data[index] = 1;
  }

  DerivedType operator/(const double& data) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] / data;
    }
    return new_array;
  }

  friend std::ostream& operator<<( std::ostream &output, const Array &array ) {
    unsigned int column; output << "(";
    for( column=0; column < array_width; column++ ) {
      output << array._data[column];
      if( column != array_width-1 ) {
        output << ", ";
      }
    }
    output << ")"; return output;
  }
};

struct Coordinate : public Array<3, double, Coordinate> {
  typedef Array< 3, double, Coordinate > SuperClass;
  double& x;
  double& y;
  double& z;

  Coordinate() : SuperClass{}, x{this->_data[0]}, y{this->_data[1]}, z{this->_data[2]} {}
};

int main() {
  Coordinate coordinate;
  std::cout << "coordinate: " << coordinate << std::endl;

  Coordinate new_coordinate = coordinate / 10.0;
  std::cout << "new_coordinate: " << new_coordinate << std::endl;
}

Однако в этой реализации используется шаблон шаблона Curily Recurring Template , имеющий ограничение,Я не могу найти способ напрямую создать экземпляр массива базового класса Array.Например, если я попробую следующее:

int main() {
  Array<5, int> int_array;
  std::cout << "int_array: " << int_array << std::endl;

  Array<5, int> new_int_array = int_array / 10;
  std::cout << "new_int_array: " << new_int_array << std::endl;
}

Компилятор скажет:

test.cpp: In function 'int main()':
test.cpp:45:15: error: wrong number of template arguments (2, should be 3)
   Array<5, int> int_array;
               ^
test.cpp:6:8: note: provided for 'template<unsigned int array_width, class DataType, class DerivedType> struct Array'
 struct Array {
        ^~~~~
test.cpp:48:15: error: wrong number of template arguments (2, should be 3)
   Array<5, int> new_int_array = int_array / 10;
               ^
test.cpp:6:8: note: provided for 'template<unsigned int array_width, class DataType, class DerivedType> struct Array'
 struct Array {
        ^~~~~

Затем я попытался передать собственный класс шаблона в качестве аргумента по умолчанию для struct Arrayобъявление выглядит следующим образом:

template <unsigned int array_width, typename DataType, typename DerivedType>
struct Array;

template <unsigned int array_width, typename DataType, typename DerivedType=Array>
struct Array {
  std::array<DataType, array_width> _data;
  // ...

Однако я выяснил, что компилятор, по-видимому, не позволяет передавать классы шаблона в другой класс шаблона, поскольку они не определяют тип, если экземпляр не создан.

test.cpp:8:77: error: invalid use of template-name 'Array' without an argument list
 template <unsigned int array_width, typename DataType, typename DerivedType=Array>
                                                                             ^~~~~
test.cpp:8:77: note: class template argument deduction is only available with -std=c++1z or -std=gnu++1z
test.cpp:6:8: note: 'template<unsigned int array_width, class DataType, class DerivedType> struct Array' declared here
 struct Array;
        ^~~~~
test.cpp: In function 'int main()':
test.cpp:48:15: error: template argument 3 is invalid
   Array<5, int> int_array;
               ^
test.cpp:51:15: error: template argument 3 is invalid
   Array<5, int> new_int_array = int_array / 10;

Следовательно, это кажется парадоксом, потому что я не могу создать экземпляр себя с собой, не зная заранее своего полного определения.Затем я попытался создать фиктивный тип с именем ConcreteArray следующим образом:

struct ConcreteArray
{
};

template <unsigned int array_width, typename DataType, typename DerivedType=ConcreteArray>
struct Array {
  std::array<DataType, array_width> _data;
  // ...

Но это создает проблемы при непосредственном создании экземпляра класса Array в качестве возвращаемого типа реализованными операторами в качестве деления.operator/ не является правильным экземпляром в качестве производного типа класса:

test.cpp: In function 'int main()':
test.cpp:52:43: error: conversion from 'ConcreteArray' to non-scalar type 'Array<5, int>' requested
   Array<5, int> new_int_array = int_array / 10;
                                 ~~~~~~~~~~^~~~
test.cpp: In instantiation of 'DerivedType Array<array_width, DataType, DerivedType>::operator/(const double&) [with unsigned int array_width = 5; DataType = int; DerivedType = ConcreteArray]':
test.cpp:52:45:   required from here
test.cpp:22:17: error: 'struct ConcreteArray' has no member named '_data'
       new_array._data[column] = _data[column] / data;
       ~~~~~~~~~~^~~~~

Как создать базовый класс при использовании шаблонов с любопытством повторяющихся шаблонов?

Ссылки:

  1. C ++ статический полиморфизм (CRTP) и использование typedefs из производных классов
  2. Любопытно повторяющийся шаблон Pattern и статика в базовом классе

Ответы [ 2 ]

0 голосов
/ 25 ноября 2018

Существует нечто несимметричное в использовании Array в качестве DerivedType в некоторых случаях при использовании фактического производного типа в других случаях, как вы представили в своем ответе.

Я хотел бы предложитьРешение, которое использует другой подход.Он использует «пустой производный тип» для случаев, когда «производный тип» не существует.

#include <iostream>
#include <array>

template <unsigned int array_width, typename DataType>
struct empty_derived_type;

template 
  <
    unsigned int array_width, 
    typename DataType, 
    typename DerivedType = empty_derived_type<array_width, DataType>
  >
struct Array {
  std::array<DataType, array_width> _data;

  Array() {
    for(unsigned int index = 0; index < array_width; ++index) _data[index] = 1;
  }

  DerivedType operator/(const double& data) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] / data;
    }
    return new_array;
  }

  friend std::ostream& operator<<( std::ostream &output, const Array &array ) {
    unsigned int column; output << "(";
    for( column=0; column < array_width; column++ ) {
      output << array._data[column];
      if( column != array_width-1 ) {
        output << ", ";
      }
    }
    output << ")"; return output;
  }
};

template <unsigned int array_width, typename DataType>
struct empty_derived_type : public Array
    <
      array_width, 
      DataType, 
      empty_derived_type<array_width, DataType>
    >
{
};

struct Coordinate : public Array<3, double, Coordinate> {
  typedef Array< 3, double, Coordinate > SuperClass;
  double& x;
  double& y;
  double& z;

  Coordinate() : SuperClass{}, x{this->_data[0]}, y{this->_data[1]}, z{this->_data[2]} {}
};

int main() {
  Coordinate coordinate;
  std::cout << "coordinate: " << coordinate << std::endl;

  Coordinate new_coordinate = coordinate / 10.0;
  std::cout << "new_coordinate: " << new_coordinate << std::endl;

  Array<5, int> int_array;
  std::cout << "int_array: " << int_array << std::endl;

  Array<5, int> new_int_array = int_array / 10;
  std::cout << "new_int_array: " << new_int_array << std::endl;
}

Вывод:

coordinate: (1, 1, 1)
new_coordinate: (0.1, 0.1, 0.1)
int_array: (1, 1, 1, 1, 1)
new_int_array: (0, 0, 0, 0, 0)
0 голосов
/ 25 ноября 2018

Я справляюсь с этим, устанавливая по умолчанию параметр производного класса базового класса как void, затем, когда тип является void, мы используем шаблон / мета-программирование, если переключить / переопределить тип void в качестве текущего типа базового класса.Это работает, потому что, поскольку я уже нахожусь в классе, определение класса завершено, поэтому мы можем использовать его собственное определение, которое уже завершено.

Это полный минимальный рабочий пример:

#include <array>
#include <iostream>

template<typename condition, typename Then, typename Else>
struct ARRAY_DEFAULT_IF_TYPE {
  typedef Else Result;
};

template<typename Then, typename Else>
struct ARRAY_DEFAULT_IF_TYPE<void, Then, Else> {
  typedef Then Result;
};

template <unsigned int array_width, typename DataType, typename DerivedTypeDefault=void>
struct Array {
  std::array<DataType, array_width> _data;

  typedef typename ARRAY_DEFAULT_IF_TYPE
      <
        DerivedTypeDefault,
        Array,
        DerivedTypeDefault
      >
      ::Result DerivedType;

  Array() {
    for(int index = 0; index < array_width; ++index) _data[index] = 1;
  }

  DerivedType operator/(const double& data) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] / data;
    }
    return new_array;
  }

  friend std::ostream& operator<<( std::ostream &output, const Array &array ) {
    unsigned int column; output << "(";
    for( column=0; column < array_width; column++ ) {
      output << array._data[column];
      if( column != array_width-1 ) {
        output << ", ";
      }
    }
    output << ")"; return output;
  }
};

struct Coordinate : public Array<3, double, Coordinate> {
  typedef Array< 3, double, Coordinate > SuperClass;
  double& x;
  double& y;
  double& z;

  Coordinate() : SuperClass{}, x{this->_data[0]}, y{this->_data[1]}, z{this->_data[2]} {}
};

int main() {
  Coordinate coordinate;
  std::cout << "coordinate: " << coordinate << std::endl;

  Coordinate new_coordinate = coordinate / 10.0;
  std::cout << "new_coordinate: " << new_coordinate << std::endl;

  Array<5, int> int_array;
  std::cout << "int_array: " << int_array << std::endl;

  Array<5, int> new_int_array = int_array / 10;
  std::cout << "new_int_array: " << new_int_array << std::endl;
}

Запустив его, вы увидите:

coordinate: (1, 1, 1)
new_coordinate: (0.1, 0.1, 0.1)
int_array: (1, 1, 1, 1, 1)
new_int_array: (0, 0, 0, 0, 0)

Полная реализация моего Array объекта с юнит-тестами doctest .Вам нужен заголовок "doctest.h", чтобы запустить это.

#include <array>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <limits>

/**
 * 'fabs' : ambiguous call to overloaded function when using templates
 * https://stackoverflow.com/questions/10744451/fabs-ambiguous-call-to-overloaded-function-when-using-templates
 */
#include <cmath>

// #define DOCTEST_CONFIG_DISABLE
#ifndef DOCTEST_CONFIG_DISABLE
  #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#endif
#include "doctest.h"

typedef long double big_double;
constexpr const int MATRICES_DIMENSION = 4;

template<typename condition, typename Then, typename Else>
struct ARRAY_DEFAULT_IF_TYPE {
  typedef Else Result;
};

template<typename Then, typename Else>
struct ARRAY_DEFAULT_IF_TYPE<void, Then, Else> {
  typedef Then Result;
};

/**
 * C++ static polymorphism (CRTP) and using typedefs from derived classes
 * https://stackoverflow.com/questions/6006614/c-static-polymorphism-crtp-and-using-typedefs-from-derived-classes
 */
template <unsigned int array_width, typename DataType, typename DerivedTypeDefault=void>
struct Array
{
  typedef typename ARRAY_DEFAULT_IF_TYPE<DerivedTypeDefault, Array, DerivedTypeDefault>::Result DerivedType;

  /**
   * Is it okay to inherit implementation from STL containers, rather than delegate?
   * https://stackoverflow.com/questions/2034916/is-it-okay-to-inherit-implementation-from-stl-containers-rather-than-delegate
   */
  std::array<DataType, array_width> _data;

  /**
   * std::array constructor inheritance
   * https://stackoverflow.com/questions/24280521/stdarray-constructor-inheritance
   */
  Array() {
  }

  Array(std::initializer_list< DataType > new_values) {
    unsigned int data_size = new_values.size();
    unsigned int column_index = 0;
    // std::cout << data_size << std::endl;

    if( data_size == 0 ) {
        std::cerr << "Welcome to the Ubuntu 16.04 awesome got nuts bug!\n";
        std::cerr << "Just give a look into his nonsense " << std::endl;
        std::cerr << "Array(new_values), " << "data_size: " << data_size << ", " << "array_width: " << array_width << std::endl;
    }
    else if( data_size == 1 ) {
      this->clear(*(new_values.begin()));
    }
    else {
      assert(data_size == array_width);

      for( auto column : new_values ) {
        this->_data[column_index] = column;
        column_index++;
      }
    }
  }

  /**
   * Overloads the `[]` array access operator, allowing you to access this class objects as the
   * where usual `C` arrays.
   *
   * How to implement bound checking for std::array?
   * https://stackoverflow.com/questions/49419089/how-to-implement-bound-checking-for-stdarray
   *
   * @param  line the current line you want to access
   * @return      a pointer to the current line
   */
  DataType operator[](unsigned int line) && {
    assert(line < array_width);
    return this->_data[line];
  }

  DataType const& operator[](unsigned int line) const& {
    assert(line < array_width);
    return this->_data[line];
  }

  DataType& operator[](unsigned int line) & {
    assert(line < array_width);
    return this->_data[line];
  }

  /**
   * Generic Data to Object operators.
   */
  bool operator<=(const DataType& data) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] > data ) {
        return false;
      }
    } return true;
  }

  bool operator<(const DataType& data) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] >= data ) {
        return false;
      }
    } return true;
  }

  bool operator>=(const DataType& data) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] < data ) {
        return false;
      }
    } return true;
  }

  bool operator>(const DataType& data) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] <= data ) {
        return false;
      }
    } return true;
  }

  bool operator==(const DataType& data) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] != data ) {
        return false;
      }
    } return true;
  }

  bool operator!=(const DataType& data) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] == data ) {
        return false;
      }
    } return true;
  }

  DerivedType operator-() const {
    DerivedType new_array;
    for( unsigned int index = 0; index < array_width; index++ ) {
      new_array._data[index] = -_data[index];
    }
    return new_array;
  }

  DerivedType operator+(const big_double& data) {
    DerivedType new_array;
    for( unsigned int index = 0; index < array_width; index++ ) {
      new_array._data[index] = _data[index] + data;
    }
    return new_array;
  }

  DerivedType operator-(const big_double& data) {
    DerivedType new_array;
    for( unsigned int index = 0; index < array_width; index++ ) {
      new_array._data[index] = _data[index] - data;
    }
    return new_array;
  }

  DerivedType& operator+=(const big_double& data) {
    for( unsigned int index = 0; index < array_width; index++ ) {
      this->_data[index] += data;
    }
    return *static_cast<DerivedType*>(this);
  }

  DerivedType& operator-=(const big_double& data) {
    for( unsigned int index = 0; index < array_width; index++ ) {
      this->_data[index] -= data;
    }
    return *static_cast<DerivedType*>(this);
  }

  DerivedType operator/(const double& data) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] / data;
    }
    return new_array;
  }

  DerivedType divide(const double& data) {
    DerivedType result = this->operator/(data);
    _data = result._data;
    return result;
  }

  /**
   * Object to Object operators.
   */
  bool operator<=(const Array& object) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] > object._data[index] ) {
        return false;
      }
    } return true;
  }

  bool operator<(const Array& object) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] >= object._data[index] ) {
        return false;
      }
    } return true;
  }

  bool operator>=(const Array& object) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] < object._data[index] ) {
        return false;
      }
    } return true;
  }

  bool operator>(const Array& object) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] <= object._data[index] ) {
        return false;
      }
    } return true;
  }

  bool operator==(const Array& object) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] != object._data[index] ) {
        return false;
      }
    } return true;
  }

  bool operator!=(const Array& object) const {
    for( unsigned int index = 0; index < array_width; index++ ) {
      if( this->_data[index] == object._data[index] ) {
        return false;
      }
    } return true;
  }

  template<typename BaseClass>
  DerivedType operator+(const Array< array_width, DataType, BaseClass >& array) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] + array._data[column];
    }
    return new_array;
  }

  template<typename BaseClass>
  DerivedType operator-(const Array< array_width, DataType, BaseClass >& array) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] - array._data[column];
    }
    return new_array;
  }

  template<typename BaseClass>
  DerivedType& operator+=(const Array< array_width, DataType, BaseClass >& array) {
    unsigned int column;

    for(column = 0; column < array_width; column++) {
      _data[column] += array._data[column];
    }
    return *static_cast<DerivedType*>(this);
  }

  template<typename BaseClass>
  DerivedType& operator-=(const Array< array_width, DataType, BaseClass >& array) {
    unsigned int column;

    for(column = 0; column < array_width; column++) {
      _data[column] -= array._data[column];
    }
    return *static_cast<DerivedType*>(this);
  }

  template<typename BaseClass>
  DerivedType operator*(const Array< array_width, DataType, BaseClass >& array) {
    unsigned int column;
    DerivedType new_array;

    for(column = 0; column < array_width; column++) {
      new_array._data[column] = _data[column] * array._data[column];
    }
    return new_array;
  }

  template<typename BaseClass>
  DerivedType& multiply(const Array< array_width, DataType, BaseClass >& array) {
    _data = this->operator*(array)._data;
    return *static_cast<DerivedType*>(this);
  }

  /**
   * The Array<> type includes the Matrix<> type, because you can multiply a `Array` by an `Matrix`,
   * but not a vice-versa.
   */
  template<typename BaseClass>
  DerivedType& multiply(const Array
      <
          array_width,
          Array< array_width, DataType, BaseClass >,
          Array< array_width, DataType, BaseClass >
      > matrix)
  {
    unsigned int column;
    unsigned int step;
    DataType old_array[array_width];

    for(column = 0; column < array_width; column++)
    {
      old_array  [column] = this->_data[column];
      this->_data[column] = 0;
    }

    for(column = 0; column < array_width; column++)
    {
      for(step = 0; step < array_width; step++)
      {
        this->_data[column] += old_array[step] * matrix._data[step][column];
      }
    }
    return *static_cast<DerivedType*>(this);
  }

  /**
   * Set all the values on the array to the specified single data parameter.
   *
   * @param `initial` the value to the used
   */
  void clear(const DataType initial = 0) {
    unsigned int column_index = 0;

    for( ; column_index < array_width; column_index++ ) {
      this->_data[column_index] = initial;
    }
  }

  /**
   * Prints a more beauty version of the array when called on `std::cout << array << std::end;`
   */
  friend std::ostream& operator<<( std::ostream &output, const Array &array ) {
    unsigned int column;
    output << "(";

    for( column=0; column < array_width; column++ ) {
      output << array._data[column];

      if( column != array_width-1 ) {
        output << ", ";
      }
    }

    output << ")";
    return output;
  }
};


/**
 * Overloading operators in derived class
 * https://stackoverflow.com/questions/5679073/overloading-operators-in-derived-class
 *
 * C++ static polymorphism (CRTP) and using typedefs from derived classes
 * https://stackoverflow.com/questions/6006614/c-static-polymorphism-crtp-and-using-typedefs-from-derived-classes
 */
struct Coordinate : public Array<MATRICES_DIMENSION, big_double, Coordinate>
{
  typedef Array< MATRICES_DIMENSION, big_double, Coordinate > SuperClass;

  /**
   * C++ member variable aliases?
   * https://stackoverflow.com/questions/494597/c-member-variable-aliases
   *
   * Memory allocation for references
   * https://stackoverflow.com/questions/11661266/memory-allocation-for-references
   *
   * Does reference variable occupy memory?
   * https://stackoverflow.com/questions/29322688/does-reference-variable-occupy-memory
   */
  big_double& x;
  big_double& y;
  big_double& z;
  big_double& w;

  Coordinate() :
      SuperClass{},
      x{this->_data[0]},
      y{this->_data[1]},
      z{this->_data[2]},
      w{this->_data[3]}
  {
    this->w = 1.0;
  }

  Coordinate(big_double initial) :
      SuperClass{initial},
      x{this->_data[0]},
      y{this->_data[1]},
      z{this->_data[2]},
      w{this->_data[3]}
  {
    this->w = 1.0;
  }

  Coordinate(big_double x, big_double y, big_double z) :
      SuperClass{x, y, z, 1.0},
      x{this->_data[0]},
      y{this->_data[1]},
      z{this->_data[2]},
      w{this->_data[3]}
  {
  }

  Coordinate(const Coordinate& object) :
      SuperClass{object},
      x{this->_data[0]},
      y{this->_data[1]},
      z{this->_data[2]},
      w{this->_data[3]}
  {
  }

  Coordinate& operator=(const Coordinate& object)
  {
    SuperClass::operator=(object);
    this->x = this->_data[0];
    this->y = this->_data[1];
    this->z = this->_data[2];
    this->w = this->_data[3];
    return *this;
  }

  ~Coordinate()
  {
  }

  /**
   * Data to Object operators.
   *
   * Comparing doubles
   * https://stackoverflow.com/questions/4010240/comparing-doubles
   *
   * What's a good way to check for ``close enough'' floating-point equality?
   * http://c-faq.com/fp/fpequal.html
   */
  bool operator==(const big_double& data) const
  {
    for( unsigned int index = 0; index < MATRICES_DIMENSION; index++ )
    {
      if( this->_data[index] == data
          || std::fabs(this->_data[index] - data)
             < std::fabs( std::min( this->_data[index], data ) ) * std::numeric_limits< big_double >::epsilon() )
      {
        return false;
      }
    }
    return true;
  }

  /**
   * Object to Object precision comparison.
   */
  bool operator==(const Coordinate& object) const
  {
    for( unsigned int index = 0; index < MATRICES_DIMENSION; index++ )
    {
      if( this->_data[index] == object._data[index]
          || std::fabs(this->_data[index] - object._data[index])
             < std::fabs( std::min( this->_data[index], object._data[index] ) ) * std::numeric_limits< big_double >::epsilon() )
      {
        return false;
      }
    }
    return true;
  }
};


/**
 * C++ Matrix Class
 * https://stackoverflow.com/questions/2076624/c-matrix-class
 *
 * A proper way to create a matrix in c++
 * https://stackoverflow.com/questions/618511/a-proper-way-to-create-a-matrix-in-c
 *
 * error: incompatible types in assignment of 'long int (*)[4]' to 'long int [4][4]'
 * https://stackoverflow.com/questions/49312484/error-incompatible-types-in-assignment-of-long-int-4-to-long-int
 */
template <unsigned int matrix_width=3, unsigned int matrix_height=3, typename DataType=long int>
struct Matrix : public Array
    <
      matrix_height,
      Array< matrix_width, DataType >,
      Array< matrix_width, DataType >
    >
{
  Matrix()
  {
  }

  Matrix(const DataType initial)
  {
    this->clear(initial);
  }

  Matrix(const std::initializer_list< std::initializer_list< DataType > > raw_data)
  {
    // std::cout << raw_data.size() << std::endl;
    assert(raw_data.size() == matrix_height);

    // std::cout << raw_data.begin()->size() << std::endl;
    assert(raw_data.begin()->size() == matrix_width);

    unsigned int line_index = 0;
    unsigned int column_index;

    for( auto line : raw_data )
    {
      column_index = 0;

      for( auto column : line )
      {
        this->_data[line_index][column_index] = column;
        column_index++;
      }

      line_index++;
    }
  }

  void clear(const DataType initial=0)
  {
    unsigned int line;
    unsigned int column;

    for( line=0; line < matrix_height; line++ )
    {
      for( column=0; column < matrix_width; column++ )
      {
        this->_data[line][column] = initial;
      }
    }
  }

  void multiply(const Matrix matrix)
  {
    unsigned int line;
    unsigned int column;
    unsigned int step;
    DataType old_matrix[matrix_height][matrix_width];

    for(line = 0; line < matrix_height; line++)
    {
      for(column = 0; column < matrix_width; column++)
      {
        old_matrix[line][column] = this->_data[line][column];
        this->_data[line][column] = 0;
      }
    }

    for(line = 0; line < matrix_height; line++)
    {
      for(column = 0; column < matrix_width; column++)
      {
        for(step = 0; step < matrix_width; step++)
        {
          this->_data[line][column] += old_matrix[line][step] * matrix._data[step][column];
        }
        // std::cout << "this->_data[line][column] = " << this->_data[line][column] << std::endl;
      }
    }
    // If you would like to preserve the original value, it can be returned here
    // return old_matrix;
  }

  /**
   * Prints a more beauty version of the matrix when called on `std::cout<< matrix << std::end;`
   */
  friend std::ostream& operator<<( std::ostream &output, const Matrix &matrix )
  {
    unsigned int line;
    unsigned int column;
    output << "{";

    for( line=0; line < matrix_height; line++ )
    {
      output << "(";

      for( column=0; column < matrix_width; column++ )
      {
        output << matrix._data[line][column];

        if( column != matrix_width-1 )
        {
          output << ", ";
        }
      }

      if( line != matrix_height-1 )
      {
        output << "), ";
      }
      else
      {
        output << ")";
      }
    }

    output << "}";
    return output;
  }
};


struct MatrixForm : public Matrix<MATRICES_DIMENSION, MATRICES_DIMENSION, big_double>
{
  // Inheriting constructors
  // https://stackoverflow.com/questions/347358/inheriting-constructors
  using Matrix< MATRICES_DIMENSION, MATRICES_DIMENSION, big_double >::Matrix;
};


TEST_CASE("Testing basic coordinate initialization with a constant value")
{
  Coordinate coordinate{2};
  std::ostringstream contents;

  contents << coordinate;
  CHECK( "(2, 2, 2, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate sum by scalar") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};

  Coordinate new_coordinate = coordinate + 10.0;
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(11, 11, 11, 11)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(1, 1, 1, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate sum and attribution by scalar") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};

  coordinate += 10.0;
  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(11, 11, 11, 11)" == contents.str() );
}

TEST_CASE("Testing basic coordinate sum by another coordinate") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};
  Coordinate another_coordinate{2.0};

  Coordinate new_coordinate = coordinate + another_coordinate;
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(3, 3, 3, 2)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(1, 1, 1, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate sum and attribution by another coordinate") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};
  Coordinate another_coordinate{2.0};

  coordinate += another_coordinate;
  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(3, 3, 3, 2)" == contents.str() );
}

TEST_CASE("Testing basic coordinate negative operator") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};

  Coordinate new_coordinate = -coordinate;
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(-1, -1, -1, -1)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(1, 1, 1, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate difference by scalar") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};

  Coordinate new_coordinate = coordinate - 10.0;
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(-9, -9, -9, -9)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(1, 1, 1, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate difference and attribution by scalar") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};

  coordinate -= 10.0;
  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(-9, -9, -9, -9)" == contents.str() );
}

TEST_CASE("Testing basic coordinate difference by another coordinate") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};
  Coordinate another_coordinate{2.0};

  Coordinate new_coordinate = coordinate - another_coordinate;
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(-1, -1, -1, 0)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(1, 1, 1, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate difference and attribution by another coordinate") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};
  Coordinate another_coordinate{2.0};

  coordinate -= another_coordinate;
  std::ostringstream().swap(contents); contents << another_coordinate;
  CHECK( "(2, 2, 2, 1)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(-1, -1, -1, 0)" == contents.str() );
}

TEST_CASE("Testing basic coordinate multiplication") {
  std::ostringstream contents;

  Coordinate coordinate1{1};
  Coordinate coordinate2{2};

  coordinate1.multiply(coordinate1);
  std::ostringstream().swap(contents); contents << coordinate1;
  CHECK( "(1, 1, 1, 1)" == contents.str() );

  coordinate1.multiply(coordinate2);
  std::ostringstream().swap(contents); contents << coordinate2;
  CHECK( "(2, 2, 2, 1)" == contents.str() );
}

TEST_CASE("Testing basic coordinate division by scalar") {
  std::ostringstream contents;
  Coordinate coordinate{1.0};

  Coordinate new_coordinate = coordinate / 10.0;
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(0.1, 0.1, 0.1, 0.1)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(1, 1, 1, 1)" == contents.str() );

  new_coordinate = coordinate.divide(100.0);
  std::ostringstream().swap(contents); contents << new_coordinate;
  CHECK( "(0.01, 0.01, 0.01, 0.01)" == contents.str() );

  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(0.01, 0.01, 0.01, 0.01)" == contents.str() );
}

TEST_CASE("Testing basic array division by scalar") {
  std::ostringstream contents;

  Array<5, double> array{1};
  std::ostringstream().swap(contents); contents << array;
  CHECK( "(1, 1, 1, 1, 1)" == contents.str() );

  Array<5, double> new_array = array / 10.0;
  std::ostringstream().swap(contents); contents << new_array;
  CHECK( "(0.1, 0.1, 0.1, 0.1, 0.1)" == contents.str() );
}

TEST_CASE("Testing basic matrix multiplication") {
  std::ostringstream contents;

  Coordinate coordinate{2};
  MatrixForm matrix{
    {1, 0, 0, 0},
    {0, 1, 0, 0},
    {0, 0, 1, 0},
    {0, 0, 0, 1}
  };

  matrix.multiply(matrix);
  coordinate.multiply(matrix);

  // https://stackoverflow.com/questions/2848087/how-to-clear-stringstream
  std::ostringstream().swap(contents); contents << coordinate;
  CHECK( "(2, 2, 2, 1)" == contents.str() );

  std::ostringstream().swap(contents); contents << matrix;
  CHECK( "{(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)}" == contents.str() );
}

Вы можете построить его с помощью:

g++ -o test application.cpp --std=c++11

Запустив его, вы увидите:

[doctest] doctest version is "2.0.1"
[doctest] run with "--help" for options
===============================================================================
[doctest] test cases:     14 |     14 passed |      0 failed |      0 skipped
[doctest] assertions:     26 |     26 passed |      0 failed |
[doctest] Status: SUCCESS!
[Finished in 5.2s]
...