Reputation: 9466
I am building my own Array implementation with several new features support and operators. I researched a lot about extending std::array
and on the end, it causes so much problem, and I decided to use composition instead of inheritance.
Following, we can see a small fraction of my Array
custom implementation with template meta programming. On this simple version, there is a print method for std::ostream
and a simple operator/
definition:
#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;
}
However, this implementation uses the Curiously Recurring Template Pattern has a limitation. I cannot find a way to directly instantiate an array of the base class Array
. For example, if I try the following:
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;
}
The compiler says:
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 {
^~~~~
Then, I tried to pass the own template class as a default argument for the struct Array
declaration as follows:
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;
// ...
However, I figured out the compiler seems to not allow to pass template classes to another template class, because they do not define a type if the are not instantiated.
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;
Hence, it seems a paradox because I cannot instantiate the myself with myself without knowing my complete definition beforehand. Then, I tried to create a dummy type called ConcreteArray
as next:
struct ConcreteArray
{
};
template <unsigned int array_width, typename DataType, typename DerivedType=ConcreteArray>
struct Array {
std::array<DataType, array_width> _data;
// ...
But, this create problems when directly instantiating the Array
class, as the returned type by the implemented operators as the division operator/
is not the correct instantiated as the derived class type:
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;
~~~~~~~~~~^~~~~
How to instantiate the base class when using the Curiously Recurring Template Pattern?
References:
Upvotes: 2
Views: 571
Reputation: 9466
I manage to it by defaulting the base class derived class parameter as void, then, when the type is void, we use a template/meta-programmed if to switch/redefine the void type as the current base class type. This works because as I am already inside the class, the class definition is complete, then, we can use its own definition which is complete now.
This is a full minimal working example:
#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;
}
Running it you will see:
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)
Full implementation of my Array
object with Unit Tests by doctest. You need the "doctest.h"
header to run this.
#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() );
}
You can build it with:
g++ -o test application.cpp --std=c++11
Running it you will see:
[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]
Upvotes: -1
Reputation: 206697
There is something unsymmetric about using Array
as the DerivedType
in some cases while using the actual derived type in other cases, as you have presented in your answer.
I would like to suggest a solution that uses a different approach. It uses an "empty derived type" for cases where a "derived type" does not exist.
#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;
}
The output:
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)
Upvotes: 2