关于C++:如何在使用奇怪的重复模板模式时实例化基类?

How to instantiate the base class when using the Curiously Recurring Template Pattern?

我正在用几个新的功能支持和运算符构建自己的数组实现。我对扩展std::array做了很多研究,最后,它造成了很多问题,我决定使用组合而不是继承。

接下来,我们可以看到我的Array自定义实现中的一小部分使用模板元编程。在这个简单的版本中,有一个用于std::ostream的打印方法和一个简单的operator/定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include
#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;
}

但是,此实现使用奇怪的重复模板模式有一个限制。我找不到直接实例化基类Array数组的方法。例如,如果我尝试以下操作:

1
2
3
4
5
6
7
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;
}

编译器说:

1
2
3
4
5
6
7
8
9
10
11
12
13
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声明的默认参数传递,如下所示:

1
2
3
4
5
6
7
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;
  // ...

但是,我发现编译器似乎不允许将模板类传递给另一个模板类,因为如果没有实例化,它们不会定义类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
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的虚拟类型,如下所示:

1
2
3
4
5
6
7
8
struct ConcreteArray
{
};

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

但是,当直接实例化Array类时,这会产生问题,因为实现的运算符返回的类型作为除法operator/不是正确的实例化为派生类类型:

1
2
3
4
5
6
7
8
9
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::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;
       ~~~~~~~~~~^~~~~

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

参考文献:

  • C++静态多态(CRTP)和使用派生类的Type DEFS
  • 基类中奇怪的循环模板模式和静态

  • 在某些情况下,使用Array作为DerivedType,而在其他情况下使用实际派生类型,这是不对称的,正如您在回答中介绍的那样。

    我想建议一个使用不同方法的解决方案。对于"派生类型"不存在的情况,它使用"空派生类型"。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include <iostream>
    #include

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

    template
      <
        unsigned int array_width,
        typename DataType,
        typename DerivedType = empty_derived_type
      >
    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
        >
    {
    };

    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;
    }

    输出:

    1
    2
    3
    4
    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)


    我通过将基类派生类参数默认为void来管理它,然后,当类型为void时,我们使用一个模板/元编程if来切换/重新定义void类型作为当前的基类类型。这是因为我已经在类中了,类定义是完整的,然后我们可以使用它自己的定义,现在它已经完成了。

    这是一个完整的最小工作示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include
    #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;
    }

    运行它,您将看到:

    1
    2
    3
    4
    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)

    通过doctest对我的Array对象进行单元测试的完整实现。您需要"doctest.h"头来运行这个。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    589
    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    #include
    #include <cassert>
    #include <iostream>
    #include
    #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!
    "
    ;
            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() );
    }

    您可以使用以下方法构建它:

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

    运行它,您将看到:

    1
    2
    3
    4
    5
    6
    7
    [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]