代码之家  ›  专栏  ›  技术社区  ›  Evandro Coan

当使用奇怪的循环模板模式时,如何实例化基类?

  •  2
  • Evandro Coan  · 技术社区  · 6 年前

    我正在构建自己的阵列实现,其中包含几个新功能和支持操作符。我 researched a lot 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;
    }
    

    但是,此实现使用 Curiously Recurring Template Pattern 有一个限制。我找不到直接实例化基类数组的方法 排列

    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;
      // ...
    

    类,作为实现的运算符作为除法返回的类型 接线员/ 不是正确的实例化为派生类类型:

    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++ static polymorphism (CRTP) and using typedefs from derived classes
    2. Curiously Recurring Template Pattern and statics in the base class
    2 回复  |  直到 6 年前
        1
  •  2
  •   Evandro Coan    6 年前

    在使用时有一些不对称之处 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)
    
        2
  •  -1
  •   Evandro Coan    6 年前

    我通过将基类派生类参数默认为void来实现它,然后,当类型为void时,我们使用一个模板/元编程if来切换/重新定义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]