I am trying to play with CRTP with a custom matrix class. Now I am trying to overloading the ostream operator, follow https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx.
However, everything seems to be compiling but the program never exists and prints nothing on the screen. I am scratching my head as what is happening.
Anyway, this is the relevant code (Sorry it is a bit lengthy)
#ifndef EXPERIMENT_POINTERMATRIX_H
#define EXPERIMENT_POINTERMATRIX_H
#include <cstdlib>
#include <ostream>
#include "macro.h"
namespace PM {
template<typename T, typename Derived>
class MatrixBase{
public:
size_t nRow;
size_t nCol;
MatrixBase(const size_t nRow_,const size_t nCol_):nRow(nRow_), nCol(nCol_){}
Derived& derived(){
return *static_cast<Derived*>(this);
}
const Derived& derived() const{
return *static_cast<Derived*>(this);
}
T& operator()(const size_t i, const size_t j){
CHECK_BOUND(i,j,*this);
return derived().operator()(i,j);
}
const T& operator()(const size_t i, const size_t j) const {
return const_cast<T&>(
static_cast<const MatrixBase<T, Derived>&>(*this).operator()(i,j));
}
inline T rows(){
return nRow;
}
const T rows() const {
return nRow;
}
inline T cols(){
return nCol;
}
const T cols() const {
return nCol;
}
template<typename t1, typename t2>
friend std::ostream& operator<<(std::ostream& os, const MatrixBase<t1, t2> & matrix);
};
template<typename t1, typename t2>
std::ostream& operator<<(std::ostream& os, const MatrixBase<t1, t2>& matrix){
for (size_t i =0;i<matrix.rows();i++){
os << matrix(i,0);
if (matrix.cols()>1) {
for (size_t j = 1; j < matrix.cols(); j++) {
os << "," << matrix(i, j);
}
}
os << std::endl;
}
return os;
};
template<typename T, typename Derived>
class Matrix : public MatrixBase<T, Matrix<T, Derived>>{
public:
T * data;
Matrix(const size_t nRow, const size_t nCol):MatrixBase<T, Matrix<T, Derived>>(nRow, nCol){
data = (T*) malloc(sizeof(T)*nRow*nCol);
}
~Matrix(){
free(data);
}
Derived& derived(){
return *static_cast<Derived*>(this);
}
const Derived& derived() const{
return *static_cast<Derived*>(this);
}
T& operator()(const size_t i, const size_t j){
return derived().operator()(i,j);
}
};
template<typename T, typename Derived>
class MatrixView : public MatrixBase<T, MatrixView<T, Derived>>{
public:
T * data;
MatrixView(const size_t nRow, size_t nCol, T * other):MatrixBase<T, MatrixView<T, Derived>>(nRow, nCol), data(other){}
T& operator()(const size_t i, const size_t j){
return derived().operator()(i,j);
}
Derived& derived(){
return *static_cast<Derived*>(this);
}
const Derived& derived() const{
return *static_cast<Derived*>(this);
}
};
template<typename T>
class MatrixRowMajor: public Matrix<T, MatrixRowMajor<T>>{
public:
MatrixRowMajor(const size_t nRow, const size_t nCol):Matrix<T, MatrixRowMajor<T>>(nRow, nCol){}
T& operator()(const size_t i, const size_t j){
using base = MatrixBase<T, Matrix<T, MatrixRowMajor<T>>>;
using super = Matrix<T, MatrixRowMajor<T>>;
return super::data[i*base::nCol+j];
}
};
template<typename T>
class MatrixColMajor: public Matrix<T, MatrixColMajor<T>>{
public:
MatrixColMajor(const size_t nRow, const size_t nCol):Matrix<T, MatrixColMajor<T>>(nRow, nCol){}
T& operator()(const size_t i, const size_t j){
using base = MatrixBase<T, Matrix<T, MatrixColMajor<T>>>;
using super = Matrix<T, MatrixColMajor<T>>;
return super::data[i+j*base::nRow];
}
};
template<typename T>
class MatrixViewRowMajor : public MatrixView<T, MatrixViewRowMajor<T>>{
public:
MatrixViewRowMajor(const size_t nRow, const size_t nCol, T* other):MatrixView<T, MatrixViewRowMajor<T>>(nRow, nCol, other){}
T& operator()(const size_t i, const size_t j){
using base = MatrixBase<T, Matrix<T, MatrixViewRowMajor<T>>>;
using super = MatrixView<T, MatrixViewRowMajor<T>>;
return super::data[i*base::nCol+j];
}
};
template<typename T>
class MatrixViewColMajor : public MatrixView<T, MatrixViewColMajor<T>>{
public:
MatrixViewColMajor(const size_t nRow, const size_t nCol, T* other):MatrixView<T, MatrixViewRowMajor<T>>(nRow, nCol, other){}
T& operator()(const size_t i, const size_t j){
using base = MatrixBase<T, Matrix<T, MatrixViewRowMajor<T>>>;
using super = MatrixView<T, MatrixViewRowMajor<T>>;
return super::data[i+j*base::nRow];
}
};
}
void test_print(){
using namespace PM;
using namespace std;
MatrixRowMajor<double> matrix(10, 1);
for (int i =0;i<matrix.rows();i++){
matrix(i,0)=1.0;
std::cout << "i'th entry is " <<matrix(i,0) << std::endl; //This is fine
}
std::cout << matrix; //This is not fine
}
#endif //EXPERIMENT_POINTERMATRIX_H
The whole program is compiled using g++4.9 (enabling c++11)
EDIT:
In order to test whether it is the problem of the operator, I create the following methods (at MatrixBase):
void print(){
for (size_t i =0;i<this->rows();i++){
std::cout << this->operator()(i,0);
if (this->cols()>1) {
for (size_t j = 1; j < this->cols(); j++) {
std::cout << "," << this->operator()(i, j);
}
}
std::cout << std::endl;
}
}
and call the methods like matrix.print(). This works as expected.
Unfortunately, the debugger gives no useful information as the program stops in the line os << matrix(i,0), and when I stop the program to retrieve the information, it says fails to get the frame (Clion as debugger)
The (member function of MatrixBase) operator() unconditionally calls itself, so is infinitely recursive.
const T& operator()(const size_t i, const size_t j) const {
return const_cast<T&>(
static_cast<const MatrixBase<T, Derived>&>(*this).operator()(i,j));
}
This function is tail-recursive, so can result in an infinite loop rather than crashing due to great call depths.
Unrelated to your question, it is usually considered inadvisable to use malloc() in C++ - particularly when working with types (e.g. C++ classes) that may not be directly compatible with C - results, for the user of your class, depending on the type T, can be undefined. Use operator new instead. Even better, use a standard container.
Related
I am writing a matrix library for generic types using expression templates.
The basic matrix class is a template class Matrix <typename Scalar, int RowSize, int ColumnSize>
which inherits from MatrixXpr< Matrix<Scalar, RowSize, ColumnSize> >
where MatrixXpr is the parent class for the expression templates "MatrixSum", "MatrixProduct" etc.
For example:
template <typename Mat, typename Rix>
class MatrixProduct : public MatrixXpr< MatrixProduct<Mat,Rix> >
{
private:
const Mat& A_;
const Rix& B_;
public:
using value_type= std::common_type_t<typename Mat::value_type, typename Rix::value_type>;
MatrixProduct(const Mat& A, const Rix& B) : A_(A), B_(B) {}
value_type operator()(int i, int j) const {
value_type out{ 0 };
for (int k = 0; k < A_.Columns(); ++k) out += A_(i, k) * B_(k, j);
return out;
}
};
The * operator is then defined outside
template <typename Mat, typename Rix>
MatrixProduct<Mat, Rix> inline const operator*(const MatrixXpr<Mat>& A, const MatrixXpr<Rix>& B)
{
return MatrixProduct<Mat, Rix>(A, B);
}
Now I wish to implement also a Scalar*Matrix class. But I fail to define the correct value_type:
template <typename Scalar, typename Mat>
class ScalarMatrixProduct : public MatrixXpr< ScalarMatrixProduct<Scalar, Mat> >
{
private:
const Scalar& A_;
const Mat& B_;
public:
using value_type = std::common_type_t<typename Mat::value_type, typename Scalar>;
ScalarMatrixProduct(const Scalar& A, const Mat& B) : A_(A), B_(B) {}
value_type operator()(int i, int j) const {
return A_ * B_(i, j);
}
};
template <typename Scalar, typename Mat>
typename std::enable_if < (!is_matrix<Scalar>::value),
ScalarMatrixProduct<Scalar, Mat > >::type const operator*(const Scalar& A, const MatrixXpr<Mat>& B)
{
return ScalarMatrixProduct<Scalar, Mat>(A, B);
}
On Mac and Linux I get an compilation error of this sort:
template argument 2 is invalid 102 | using value_type =
std::common_type_t<typename Mat::value_type, typename Scalar>;
Interestingly, it compiles on Windows.
Any hints for what's wrong would be helpful.
Thanks in advance.
Complete example:
#include <type_traits>
#include <iostream>
#include <array>
#include <initializer_list>
///////////Expression Template Base Class for CRTP
template <class MatrixClass> struct MatrixXpr {
decltype(auto) operator()(int i, int j) const {
return static_cast<MatrixClass const&>(*this)(i, j);
}
operator MatrixClass& () {
return static_cast<MatrixClass&>(*this);
}
operator const MatrixClass& () const {
return static_cast<const MatrixClass&>(*this);
}
int Rows()
{
return static_cast<MatrixClass&>(*this).Rows();
}
int Columns()
{
return static_cast<MatrixClass&>(*this).Columns();
}
int Rows() const
{
return static_cast<const MatrixClass&>(*this).Rows();
}
int Columns() const
{
return static_cast<const MatrixClass&>(*this).Columns();
}
friend int Rows(const MatrixXpr& A)
{
return A.Rows();
}
friend int Columns(const MatrixXpr& A)
{
return A.Columns();
}
};
template <typename MatrixClass>
std::ostream& operator<<(std::ostream& os, const MatrixXpr<MatrixClass>& A)
{
for (int r = 0; r < Rows(A); ++r) {
os << '[';
for (int c = 0; c < Columns(A); ++c)
os << A(r, c) << (c + 1 < Columns(A) ? " " : "");
os << "]\n";
}
return os;
}
/////////// Matrix Product
template <typename Mat, typename Rix>
class MatrixProduct : public MatrixXpr< MatrixProduct<Mat, Rix> >
{
private:
const Mat& A_;
const Rix& B_;
public:
using value_type = std::common_type_t<typename Mat::value_type, typename Rix::value_type>;
MatrixProduct(const Mat& A, const Rix& B) : A_(A), B_(B)
{
std::cout << "MatrixMatrixProduct Constructor\n";
}
int Rows() const { return A_.Rows(); }
int Columns() const { return B_.Columns(); }
value_type operator()(int i, int j) const {
value_type out{ 0 };
for (int k = 0; k < A_.Columns(); ++k) out += A_(i, k) * B_(k, j);
return out;
}
};
/////////// Scalar Matrix Product
template <typename Scalar, typename Mat>
class ScalarMatrixProduct : public MatrixXpr< ScalarMatrixProduct<Scalar, Mat> >
{
private:
const Scalar& A_;
const Mat& B_;
public:
using value_type = std::common_type_t<typename Mat::value_type, typename Scalar>;
ScalarMatrixProduct(const Scalar& A, const Mat& B) : A_(A), B_(B) {
std::cout << "ScalarMatrixProduct Constructor\n";
}
int Rows() const { return B_.Rows(); }
int Columns() const { return B_.Columns(); }
value_type operator()(int i, int j) const {
return A_ * B_(i, j);
}
};
//The following two functions are Helpers for initializing an array.
//Source: https://stackoverflow.com/a/38934685/6176345
template<typename T, std::size_t N, std::size_t ...Ns>
std::array<T, N> make_array_impl(
std::initializer_list<T> list,
std::index_sequence<Ns...>)
{
return std::array<T, N>{ *(list.begin() + Ns) ... };
}
template<typename T, std::size_t N>
std::array<T, N> make_array(std::initializer_list<T> list) {
if (N > list.size())
throw std::out_of_range("Initializer list too small.");
return make_array_impl<T, N>(list, std::make_index_sequence<N>());
}
/////////// Matrix class
template <typename Scalar, int RowSize, int ColumnSize = RowSize>
class Matrix : public MatrixXpr< Matrix<Scalar, RowSize, ColumnSize> >
{
std::array<Scalar, RowSize* ColumnSize> data_;
public:
using value_type = Scalar;
const static int rows_ = RowSize;
const static int columns_ = ColumnSize;
int Rows() const { return rows_; }
int Columns() const { return columns_; }
Matrix() : data_{ Scalar(0) } {};
Matrix(const Matrix& other) = default;
Matrix(Matrix&& other) = default;
Matrix& operator=(const Matrix& other) = default;
Matrix& operator=(Matrix&& other) = default;
~Matrix() = default;
Matrix(std::initializer_list<Scalar> data) : data_(make_array<Scalar, RowSize* ColumnSize>(data)) {}
template <typename Source>
Matrix& operator=(const MatrixXpr<Source>& source)
{
for (int i = 0; i < rows_; ++i)
for (int j = 0; j < columns_; ++j)
data_[MatrixIndex(i, j)] = source(i, j);
return *this;
}
template <typename Source>
Matrix(const MatrixXpr<Source>& source)
{
for (int i = 0; i < rows_; ++i)
for (int j = 0; j < columns_; ++j)
data_[MatrixIndex(i, j)] = source(i, j);
}
Scalar& operator()(int i, int j) {
return data_[MatrixIndex(i, j)];
}
const Scalar& operator()(int i, int j) const {
return data_[MatrixIndex(i, j)];
}
private:
inline static int MatrixIndex(int i, int j)
{
return i * columns_ + j;
}
};
/////////// Multiplication operators
template <typename Mat, typename Rix>
MatrixProduct<Mat, Rix> inline const operator*(const MatrixXpr<Mat>& A, const MatrixXpr<Rix>& B)
{
std::cout << "Matrix Matrix Multiplication\n";
return MatrixProduct<Mat, Rix>(A, B);
}
template <typename Scalar, typename Mat>
typename std::enable_if_t<!std::is_base_of_v<MatrixXpr<Scalar>, Scalar>,
ScalarMatrixProduct<Scalar, Mat >> const operator*(const Scalar& A, const MatrixXpr<Mat>& B)
{
return ScalarMatrixProduct<Scalar, Mat>(A, B);
}
/////////// Failing example
int main()
{
Matrix<int, 2, 2> m = { 1,0,0,1 };
auto n = 3 * m;
std::cout << n;
std::cout << m * n;
//std::cout << n * m; // Error
return 0;
}
Edit:
The above code originally had two problems.
The first one is that my type checking failed to see which overload of the *operator was being used. The above implementation with std::is_base_of_v<MatrixXpr<Scalar>, Scalar> fixed it and is is working correctly.
I do not know why this old code did not work. Here is the old version:
template <typename T>
struct is_matrix : std::false_type {};
template <typename T>
struct is_matrix<const T> : is_matrix<T> {};
template <typename MatrixClass>
struct is_matrix<MatrixXpr<MatrixClass> > : std::true_type {};
template <typename Scalar, typename Mat>
typename std::enable_if < (!is_matrix<Scalar>::value),
ScalarMatrixProduct<Scalar, Mat > >::type const operator*(const Scalar& A, const MatrixXpr<Mat>& B)
{
std::cout << "Scalar Matrix Multiplication\n";
return ScalarMatrixProduct<Scalar, Mat>(A, B);
}
I have problem with implicit conversions in C++.
I'm trying to create some Expression template for vector arithmetics (I know that same libraries already exists. I'm just learning C++ so I wanted to try something with templates).
I would like to create class Vector, that is able to compute like this:
simd::test::Vector<char, 5> a;
simd::test::Vector<short, 5> b;
auto ret = a + b + a + b;
, where on output would be Vector of shorts becouse short is bigger type than char.
Right now, I have class that is able to adds vectors of same data types. For different types I have to call explicit conversion:
//simd::test::Vector<short, 5>(a)
auto ret = simd::test::Vector<short, 5>(a) + b + simd::test::Vector<short, 5>(a) + b;
Is possible to implicit convert Vector before pass into function "operator+()"? Here is my code of Vector:
#pragma once
#include <type_traits>
namespace simd {
namespace test {
template<typename R, std::size_t Dim,
typename std::enable_if<std::is_arithmetic<R>::value>::type* = nullptr
>
class Vector_expression {
public:
static constexpr std::size_t size = Dim;
virtual const R operator[] (std::size_t index) const = 0;
virtual ~Vector_expression() = default;
};
template<typename T, std::size_t Dim>
class Vector final : public Vector_expression<T, Dim> {
private:
T data[Dim];
public:
Vector() = default;
template<typename R>
Vector(const Vector_expression<R, Dim> &obj) {
for(std::size_t index = 0; index < Dim; ++index) {
data[index] = obj[index];
}
}
template<typename R>
Vector(Vector_expression<R, Dim> &&obj) {
for(std::size_t index = 0; index < Dim; ++index) {
data[index] = obj[index];
}
}
template<typename R>
Vector<T, Dim> & operator=(const Vector_expression<R, Dim> &obj) {
for(std::size_t index = 0; index < Dim; ++index) {
data[index] = obj[index];
}
return (*this);
}
template<typename R>
Vector<T, Dim> & operator=(Vector_expression<R, Dim> && obj) {
for(std::size_t index = 0; index < Dim; ++index) {
data[index] = obj[index];
}
return (*this);
}
virtual const T operator[] (std::size_t index) const override {
return data[index];
}
T & operator[] (std::size_t index) {
return data[index];
}
virtual ~Vector() = default;
};
template<typename E1, typename E2, typename R, std::size_t Dim>
class Vector_sum final : public Vector_expression<R, Dim> {
private:
const E1 & _lhs;
const E2 & _rhs;
public:
Vector_sum() = delete;
Vector_sum(const E1 & lhs, const E2 & rhs) :
_lhs(lhs),
_rhs(rhs)
{}
virtual const R operator[] (std::size_t index) const override {
return _lhs[index] + _rhs[index];
}
virtual ~Vector_sum() = default;
};
template<typename R, std::size_t Dim>
Vector_sum<Vector_expression<R, Dim>, Vector_expression<R, Dim>, R, Dim> operator+ (const Vector_expression<R, Dim> & lhs, const Vector_expression<R, Dim> & rhs) {
return {lhs, rhs};
}
}
}
Just define an operator+ that allows different argument types. The one catch is determining the element type of the resulting sum. Probably the best option is to use whatever the result of adding two elements is. One way to write this type is:
decltype(std::declval<const R1>() + std::declval<const R2>())
Or if you know the types are built-in arithmetic types, that would be the same as
std::common_type_t<R1, R2>
Or using a trailing return type, we can take advantage of the function parameters to shorten the std::declval expressions:
template<typename R1, typename R2, std::size_t Dim>
auto operator+ (const Vector_expression<R1, Dim> & lhs,
const Vector_expression<R2, Dim> & rhs)
-> Vector_sum<Vector_expression<R1, Dim>, Vector_expression<R2, Dim>,
decltype(lhs[0] + rhs[0]), Dim>
{
return {lhs, rhs};
}
It could be done using templates and std::common_type, something like this:
template<typename T1, typename T2, size_t S>
simd::test::Vector<typename std::common_type<T1, T2>::type, S>
operator+(simd::test::Vector<T1, S> const& v1,
simd::test::Vector<T2, S> const& v2)
{
// TODO: Implementation...
}
I have little c++ experience, but now I need to look at some code that uses expression templates a lot, so I am reading chapter 18 of the book << C++ Templates: The Complete Guide >> and working on the example provided in the book. If you happened to have the book, the example starts from pp 328, with all the contextual information.
My code works fine until I want to add the support for subvector indexing (pp 338), I could not get the assignment to work, g++ gives the following error:
error: binding ‘const value_type {aka const double}’ to reference of type ‘double&’ discards qualifiers
return v[vi[idx]];
I have no idea what's going on, am I assigning to a constant object? How do I make this work? Here is my code:
#include <iostream>
#include <vector>
template<typename T>
class ET_Scalar {
private:
const T& s;
public:
ET_Scalar(const T& v) :
s(v) {}
T operator[](size_t) const
{
return s;
}
size_t size() const
{
return 0; // Zero means it's a scalar
}
};
template<typename T, typename V, typename VI>
class ET_SubVec {
private:
const V& v;
const VI& vi;
public:
ET_SubVec(const V& a, const VI& b) :
v(a), vi(b) {}
const T operator[] (size_t idx) const
{
return v[vi[idx]];
}
T& operator[] (size_t idx)
{
return v[vi[idx]];
}
size_t size() const
{
return vi.size();
}
};
// Using std::vector as storage
template<typename T, typename Rep = std::vector<T>>
class ET_Vector {
private:
Rep expr_rep;
public:
// Create vector with initial size
explicit ET_Vector(size_t s) :
expr_rep(s) {}
ET_Vector(const Rep& v) :
expr_rep(v) {}
ET_Vector& operator=(const ET_Vector& v)
{
for (size_t i = 0; i < v.size(); i++)
expr_rep[i] = v[i];
return *this;
}
template<typename T2, typename Rep2>
ET_Vector& operator=(const ET_Vector<T2, Rep2>& v)
{
for (size_t i = 0; i < v.size(); i++)
expr_rep[i] = v[i];
return *this;
}
size_t size() const
{
return expr_rep.size();
}
const T operator[](size_t idx) const
{
return expr_rep[idx];
}
T& operator[](size_t idx)
{
return expr_rep[idx];
}
template<typename T2, typename Rep2>
ET_Vector<T, ET_SubVec<T, Rep, Rep2>> operator[](const ET_Vector<T2, Rep2>& vi)
{
return ET_Vector<T, ET_SubVec<T, Rep, Rep2>>(ET_SubVec<T, Rep, Rep2>(expr_rep, vi.rep()));
}
template<typename T2, typename Rep2>
const ET_Vector<T, ET_SubVec<T, Rep, Rep2>> operator[](const ET_Vector<T2, Rep2>& vi) const
{
return ET_Vector<T, ET_SubVec<T, Rep, Rep2>>(ET_SubVec<T, Rep, Rep2>(expr_rep, vi.rep()));
}
// Return what the vector currently represents
const Rep& rep() const
{
return expr_rep;
}
Rep& rep()
{
return expr_rep;
}
};
template<typename T>
class ET_Traits {
public:
typedef const T& ExprRef;
};
template<typename T>
class ET_Traits<ET_Scalar<T>> {
public:
typedef ET_Scalar<T> ExprRef;
};
template<typename T, typename LHS, typename RHS>
class ET_Add {
private:
typename ET_Traits<LHS>::ExprRef lhs;
typename ET_Traits<RHS>::ExprRef rhs;
public:
ET_Add(const LHS& l, const RHS& r) :
lhs(l), rhs(r) {}
T operator[](size_t idx) const
{
return lhs[idx] + rhs[idx];
}
size_t size() const
{
return (lhs.size() != 0) ? lhs.size() : rhs.size();
}
};
template<typename T, typename LHS, typename RHS>
class ET_Mul {
private:
typename ET_Traits<LHS>::ExprRef lhs;
typename ET_Traits<RHS>::ExprRef rhs;
public:
ET_Mul(const LHS& l, const RHS& r) :
lhs(l), rhs(r) {}
T operator[](size_t idx) const
{
return lhs[idx] * rhs[idx];
}
size_t size() const
{
return (lhs.size() != 0) ? lhs.size() : rhs.size();
}
};
// Vector + Vector
template<typename T, typename LHS, typename RHS>
ET_Vector<T, ET_Add<T, LHS, RHS>>
operator+(const ET_Vector<T, LHS>& a, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Add<T, LHS, RHS>>(ET_Add<T, LHS, RHS>(a.rep(), b.rep()));
}
// Scalar + Vector
template<typename T, typename RHS>
ET_Vector<T, ET_Add<T, ET_Scalar<T>, RHS>>
operator+(const T& s, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Add<T, ET_Scalar<T>, RHS>>(ET_Add<T, ET_Scalar<T>, RHS>(ET_Scalar<T>(s), b.rep()));
}
// Vector .* Vector
template<typename T, typename LHS, typename RHS>
ET_Vector<T, ET_Mul<T, LHS, RHS>>
operator*(const ET_Vector<T, LHS>& a, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Mul<T, LHS, RHS>>(ET_Mul<T, LHS, RHS>(a.rep(), b.rep()));
}
//Scalar * Vector
template<typename T, typename RHS>
ET_Vector<T, ET_Mul<T, ET_Scalar<T>, RHS>>
operator*(const T& s, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Mul<T, ET_Scalar<T>, RHS>>(ET_Mul<T, ET_Scalar<T>, RHS>(ET_Scalar<T>(s), b.rep()));
}
template<typename T>
void print_vec(const T& e)
{
for (size_t i = 0; i < e.size(); i++) {
std::cout << e[i] << ' ';
}
std::cout << '\n';
return;
}
int main()
{
size_t N = 16;
ET_Vector<double> x(N);
ET_Vector<double> y(N);
ET_Vector<double> z(N);
ET_Vector<int> idx(N / 2);
// Do not use auto z = [expr] here! Otherwise the type of z will still be a
// container, and evaluation won't happen until later. But the compiler
// will optimize necessary information away, causing errors.
z = (6.5 + x) + (-2.0 * (1.25 + y));
print_vec(z);
for (int i = 0; i < 8; i++)
idx[i] = 2 * i;
z[idx] = -1.0 * z[idx];
print_vec(z);
return 0;
}
Sorry about its length, I've failed to create a minimal (not) working example.
I wanted to write my own Vector class template and also wanted to add some specializations, for example a 3D vector type where the components can be accessed through x/y/z.
The template and the specializations work fine so far, but the issue is, that the specialized templates require a lot of copy/pasting from the base template to work. I would like to reduce that.
This is what it looks like right now:
template<class T, unsigned int dim>
class Vector;
template<class T, unsigned int dim>
Vector<T, dim> add(Vector<T, dim> const& lhs, Vector<T, dim> const& rhs)
{
Vector<T, dim> tmp;
for (unsigned int i = 0; i < dim; ++i)
{
tmp[i] = lhs[i] + rhs[i];
}
return tmp;
}
template<class T, unsigned int dim, class S>
Vector<T, dim> add(Vector<T, dim> const& lhs, S const& rhs)
{
Vector<T, dim> tmp;
for (unsigned int i = 0; i < dim; ++i)
{
tmp[i] = lhs[i] + rhs;
}
return tmp;
}
template<class T, unsigned int dim>
Vector<T, dim> operator+(Vector<T, dim> const& lhs, Vector<T, dim> const& rhs)
{
return vectors::add(lhs, rhs);
}
template<class T, unsigned int dim, class S>
Vector<T, dim> operator+(Vector<T, dim> const& lhs, S const& rhs)
{
return vectors::add(lhs, rhs);
}
template<class T, unsigned int dim>
class Vector
{
//...
protected:
T values[dim] __attribute((aligned(16)));
public:
template<class R, unsigned int fdim>
friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, Vector<R, fdim> const& rhs);
template<class R, unsigned int fdim, class S>
friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, S const& rhs);
template<class R, unsigned int fdim, class S>
friend Vector<R, fdim> operator+(S const& lhs, Vector<R, fdim> const& rhs);
//...
//constructors, etc.
};
template<class T>
class Vector<T, 3>
{
//...
protected:
T values[3] __attribute((aligned(16)));
public:
T& x = values[0];
T& y = values[1];
T& z = values[2];
//lots of copy-pasta :(
template<class R, unsigned int fdim>
friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, Vector<R, fdim> const& rhs);
template<class R, unsigned int fdim, class S>
friend Vector<R, fdim> operator+(Vector<R, fdim> const& lhs, S const& rhs);
template<class R, unsigned int fdim, class S>
friend Vector<R, fdim> operator+(S const& lhs, Vector<R, fdim> const& rhs);
//...
//constructors, etc.
};
Now I thought the easy solution would be to simply define Vector3D as a sub-class of the Vector template, like so:
template<class T>
class Vector3D: public Vector<T, 3>
{
//...
public:
T& x = values[0];
T& y = values[1];
T& z = values[2];
//no copy-pasta :)
//...
//constructors, etc.
};
That doesn't work at all, due to ambiguity:
ambiguous overload for ‘operator+’ (operand types are ‘const vec3f {aka const math::vectors::Vector3D<float>}’ and ‘math::vectors::vec3f {aka math::vectors::Vector3D<float>}’)
../main.cpp:84:16: note: candidates are:
In file included from ../main.cpp:10:0:
../include/vector.hpp:720:16: note: math::vectors::Vector<T, dim> math::vectors::operator+(const math::vectors::Vector<T, dim>&, const math::vectors::Vector<T, dim>&) [with T = float; unsigned int dim = 3u]
Vector<T, dim> operator+(Vector<T, dim> const& lhs, Vector<T, dim> const& rhs)
^
../include/vector.hpp:726:16: note: math::vectors::Vector<T, dim> math::vectors::operator+(const math::vectors::Vector<T, dim>&, const S&) [with T = float; unsigned int dim = 3u; S = math::vectors::Vector3D<float>]
Vector<T, dim> operator+(Vector<T, dim> const& lhs, S const& rhs)
^
../include/vector.hpp:732:16: note: math::vectors::Vector<T, dim> math::vectors::operator+(const S&, const math::vectors::Vector<T, dim>&) [with T = float; unsigned int dim = 3u; S = math::vectors::Vector3D<float>]
Vector<T, dim> operator+(S const& lhs, Vector<T, dim> const& rhs)
So it seems like the template substitution fails, because S can also be substituted with the new Vector3D class as well, while it's supposed to handle only scalars.
So I tried to get rid of that issue by writing a small wrapper class for scalars like so:
template<class T>
class ScalarType
{
public:
T value;
ScalarType() :
value(0)
{
}
ScalarType(T const& _v) :
value(_v)
{
}
ScalarType(ScalarType<T> const& rhs) :
value(rhs.value)
{
}
operator T&()
{
return value;
}
operator T() const
{
return value;
}
};
And replace all instances of S const& (l|r)hs with ScalarType<S> const& (l|r)hs.
That got the operators with Vectors on both sides to work again, but the operators that are supposed to handle Vector-Scalar operations fail still.
This time it's due to the fact, that the scalar value has to be explicitly of type ScalarType, since implicit conversions to that don't work with template substitution.
So, is there any way of getting this to work at all or do I have to stick with the copy-paste code?
Done here with partial template specialisation and CRTP.
maybe_has_z<Container, N> is a class which translates Container::z() into Container::operator[](2), but only if Container::size() >= 3
#include <array>
#include <iostream>
#include <algorithm>
//
// some boilerplate - note the different indecies
//
// define some concepts
template<class Container, std::size_t N, typename= void>
struct maybe_has_x{};
template<class Container, std::size_t N, typename = void>
struct maybe_has_y{};
template<class Container, std::size_t N, typename = void>
struct maybe_has_z{};
// specialise the concepts into (sometimes) concrete accessors
template<class Container, std::size_t N>
struct maybe_has_x<Container, N, std::enable_if_t<(N > 0)>>
{
auto& x() const { return static_cast<const Container&>(*this)[0]; }
auto& x() { return static_cast<Container&>(*this)[0]; }
};
template<class Container, std::size_t N>
struct maybe_has_y<Container, N, std::enable_if_t<(N > 1)>>
{
auto& y() const { return static_cast<const Container&>(*this)[1]; }
auto& y() { return static_cast<Container&>(*this)[1]; }
};
template<class Container, std::size_t N>
struct maybe_has_z<Container, N, std::enable_if_t<(N > 2)>>
{
auto& z() const { return static_cast<const Container&>(*this)[2]; }
auto& z() { return static_cast<Container&>(*this)[2]; }
};
// define our vector type
template<class T, std::size_t N>
struct Vector
: std::array<T, N>
, maybe_has_x<Vector<T, N>, N> // include the maybe_ concepts
, maybe_has_y<Vector<T, N>, N>
, maybe_has_z<Vector<T, N>, N>
{
private:
using inherited = std::array<T, N>;
public:
Vector() : inherited {} {};
Vector(std::initializer_list<T> il)
: inherited { }
{
std::copy_n(il.begin(), std::min(il.size(), this->size()), std::begin(*this));
}
Vector(const inherited& rhs) : inherited(rhs) {}
public:
using value_type = typename inherited::value_type;
// offer arithmetic unary functions in class (example +=)
// note that this allows us to add integers to a vector of doubles
template<class Other, std::enable_if_t<std::is_convertible<value_type, Other>::value> * = nullptr>
Vector& operator+=(const Vector<Other, N>&rhs) {
auto lfirst = std::begin(*this);
auto rfirst = std::begin(rhs);
auto lend = std::end(*this);
while (lfirst != lend) {
*lfirst += *rfirst;
++lfirst;
++rfirst;
}
return *this;
}
};
// offer binary arithmetic as free functions
template<class T, std::size_t N, class Other>
Vector<T, N> operator+(Vector<T, N> lhs, const Vector<Other, N>& rhs) {
lhs += rhs;
return lhs;
}
// offer some streaming capability
template<class T, std::size_t N>
std::ostream& operator<<(std::ostream& os, const Vector<T, N>& rhs) {
auto sep = "";
os << '[';
for (auto& x : rhs) {
os << sep << x;
sep = ", ";
}
return os << ']';
}
// test
int main()
{
auto a = Vector<double, 3> { 2.1, 1.2, 3.3 };
auto b = a + a + Vector<int, 3> { 1, 1, 1 };
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << a.x() << ", " << a.y() << ", " << a.z() << std::endl;
auto c = Vector<double, 2> { 4.4, 5.5 };
std::cout << c << std::endl;
std::cout << c.x() << std::endl;
std::cout << c.y() << std::endl;
// won't compile
// std::cout << c.z() << std::endl;
}
expected output:
[2.1, 1.2, 3.3]
[5.2, 3.4, 7.6]
2.1, 1.2, 3.3
[4.4, 5.5]
4.4
5.5
I am using Boost-Operatators to construct a matrix class. (A toy project). However, I run into issues when I want to mix matrices of different element types.
Basically I have a template class Matrix<T>, where T is the element type of that matrix. I'm using Boost-Operators to define operators between instances of Matrix<T> (e.g. element-wise add), between Matrix<T> and T (e.g. scalar multiplication), and if possible also between Matrix<T> and Matrix<U> (e.g. real matrix plus complex matrix).
The boost operators support one, or two template arguments. One if you want operators between two objects of the same type, and two if you want mixed operators.
template<typename T>
class Matrix : boost::addable<Matrix<T>> // Add another matrix of same type.
boost::multiplyable2<Matrix<T>,T> // Scalar multiplication with a `T`.
However, I cannot give Matrix<U> as a second argument, because then my class would have two template arguments and the type would depend on which matrices I can operate with.
template<typename T, typename U>
class Matrix : boost::addable2<Matrix<T,U>,Matrix<U,?>> // Now I have two template arguments.
// That's certainly not what I want!
I also tried implementing my own version of boost::addable, but this didn't work either. The compiler complains about an uncomplete type.
template<class Derived>
class Addable {
template<class Other>
friend Derived operator+(Derived lhs, const Other &rhs) {
return lhs += rhs;
}
template<class Other>
friend Derived operator+(const Other &lhs, Derived rhs) {
return rhs += lhs;
}
};
Another approach was to define a cast constructor from Matrix<U> to Matrix<T>. However, now I have the issue, that those are two different types, and I don't get access to the private members. So, I either need to make more stuff public than I want to, or find a different way of doing this.
How would you implement such a thing?
The full Code
#include <cassert>
#include <utility>
#include <complex>
#include <vector>
#include <algorithm>
#include <iostream>
#include <boost/operators.hpp>
typedef double Real;
typedef std::complex<Real> Complex;
template<typename T>
class Matrix : boost::addable<Matrix<T>>
{
public:
Matrix() = default;
template<typename U>
Matrix(const Matrix<U> &other)
: m_(other.m()), n_(other.n()),
data_(other.data_.begin(), other.data_.end()) { }
Matrix(size_t m, size_t n) : m_(m), n_(n), data_(m*n) { }
Matrix(size_t m, size_t n, const T &initial)
: m_(m), n_(n), data_(m*n, initial) { }
size_t m() const { return m_; }
size_t n() const { return n_; }
size_t size() const {
assert(m_*n_ == data_.size());
return data_.size();
}
const T &operator()(size_t i, size_t j) const { return data_[i*m_ + j]; }
T &operator()(size_t i, size_t j) { return data_[i*m_ + j]; }
void fill(const T &value) {
std::fill(data_.begin(), data_.end(), value);
}
Matrix &operator+=(const Matrix &other) {
assert(dim_match(other));
for (int i = 0; i < size(); ++i) {
data_[i] += other.data_[i];
}
return *this;
}
friend std::ostream &operator<<(std::ostream &o, const Matrix &m) {
if (m.size() == 0) {
o << "()" << std::endl;
return o;
}
for (int i = 0; i < m.m(); ++i) {
o << "( ";
for (int j = 0; j < m.n() - 1; ++j) {
o << m(i,j) << ", ";
}
o << m(i, m.n() - 1) << " )" << std::endl;
}
return o;
}
private:
bool dim_match(const Matrix &other) {
return n_ == other.n_ && m_ == other.m_;
}
private:
int m_, n_;
typedef std::vector<T> Store;
Store data_;
};
int main() {
Matrix<Real> A(2,3, 1.);
Matrix<Complex> B(2,3, Complex(0,1));
auto C = Matrix<Complex>(A) + B;
std::cout << A << std::endl;
std::cout << B << std::endl;
std::cout << C << std::endl;
}
This is how I'd do it: use a friend template function (see Operator overloading: The Decision between Member and Non-member):
template<typename T>
class Matrix
{
public:
template<typename> friend class Matrix;
And then later
template <typename T1, typename T2>
Matrix<typename std::common_type<T1, T2>::type>
operator+(Matrix<T1> const& a, Matrix<T2> const& b)
{
Matrix<typename std::common_type<T1, T2>::type> result(a);
return (result += b);
}
Note the use of common_type to arrive at a sensible resulting type (you might want to introduce your own trait there to cater for your specific requirements)
See it Live On Coliru