Related
I'm having some problem with my std::forward constructor for my template "matrix" class. Basically i want to set a matrix of type float and size 4 equal to the sum of 2 matrices of type float and size 3. I do this inside of my struct 'matrix_struct' in the function 'test'. However, MSVC error tells me that "'static_cast': cannot convert from 'matrix' to 'float'" and whenever I inspect the error it takes me to the 3rd matrix constructor with std::forward.
///////////////////////////////////
somefile.hpp
#pragma once
#include "matrix.hpp"
using matrix3 = matrix<float, 3>;
using matrix4 = matrix<float, 4>;
struct matrix_struct {
matrix4 sum;
void test(const matrix3& a, const matrix3& b)
{
sum = a + b;
}
}
///////////////////////////////////
matrix.hpp
#pragma once
#include <array>
template <typename t, size_t dim>
class matrix
{
public:
matrix() { data.fill(static_cast<t>(0) }
explicit matrix(const std::array<t, dim>& a) : data(a) {}
template <typename... args_t>
matrix(args_t... args) : data{ static_cast<t>(std::forward<args_t>(args))... } }
public:
t& at(const size_t index)
{
return data.at(index >= dim ? dim - 1 : index);
}
const t& at(const size_t index) const
{
return data.at(index >= dim ? dim - 1 : index);
}
public:
matrix& operator = (const matrix<t, dim>& other)
{
for (size_t i = 0; i < dim; ++i) {
at(i) = other.at(i);
}
return *this;
}
matrix& operator = (const std::array<t, dim>& other)
{
for (size_t i = 0; i < dim; ++i) {
at(i) = other.at(i);
}
return *this;
}
matrix& operator = (const t& other)
{
for (size_t i = 0; i < dim; ++i) {
at(i) = other;
}
return *this;
}
public:
matrix operator + (const matrix<t, dim>& other) const
{
matrix<t, dim> ret;
for (size_t i = 0; i < dim; ++i) {
ret.at(i) = at(i) + other.at(i);
}
return ret;
}
matrix operator + (const std::array<t, dim>& other) const
{
matrix<t, dim> ret;
for (size_t i = 0; i < dim; ++i) {
ret.at(i) = at(i) + other.at(i);
}
return ret;
}
matrix operator + (const t& other) const
{
matrix<t, dim> ret;
for (size_t i = 0; i < dim; ++i) {
ret.at(i) = at(i) + other;
}
return ret;
}
private:
std::array<t, dim> data;
};
Template constructors are problematic. They often create code that is a better candidate than your other constructors.
The general solution is to disable the template if its decayed type matches the class you are writing.
example:
struct MyClass
{
template
<
class Arg,
class...Rest,
std::enable_if_t
<
! std::is_same
<
std::decay_t<Arg>,
MyClass
>::value
>* = nullptr
>
MyClass(Arg&& arg, Rest&&...rest)
{
// code to construct from something that's not MyClass
// this will no longer hijack copy constructors etc.
}
};
The first problem of your code sample is addressed by #RichardHodges's answer.
Assuming you include his solution to overcome tricky copy/move constructor selection, another problem remains: you do not offer a matrix promotion/demotion service through your constructors/assignment operators.
Therefore, the following line in your test function:
sum = a + b; // a + b is a matrix<float, 3>, sum a matrix<float, 4>
Will trigger a call to the variadic template constructor and fail.
Starting from Richard's solution, you need to tweak a bit the SFINAE condition to extend it to matrices of any size. To do so, we will need a little is_matrix trait:
template <typename T, size_t Dim>
class matrix;
template <typename T>
struct is_matrix : std::false_type {};
template <typename Num, size_t Size>
struct is_matrix<matrix<Num, Size> > : std::true_type {
using value_type = Num;
};
Now the variadic template constructor becomes:
template <typename t, size_t dim>
class matrix
{
/* ... */
public:
/* ... */
template
<
class Arg,
class...Rest,
std::enable_if_t
<
! std::is_matrix
<
std::decay_t<Arg>
>::value
>* = nullptr
>
matrix(Arg&& arg, Rest&&...rest)
{
// code to construct from something that's not a matrix
// this will no longer hijack copy constructors etc.
}
};
Then, we need to add the proper matrix constructor along with the proper friend declaration:
template <typename t, typename dim>
class matrix {
public:
template <typename OtherT, size_t OtherDim>
friend class matrix;
template <size_t OtherDim>
matrix(matrix<t, OtherDim> const& other) {
size_t i = 0;
for (; i < min(OtherDim, dim); ++i) {
data[i] = other.data[i];
}
for(; i < dim; ++i) {
data[i] = t();
}
}
template <typename OtherT,
size_t OtherDim>
matrix(matrix<OtherT, OtherDim> const&) {
static_assert(std::is_same<t, OtherT>::value,
"value_type mismatch between matrices!");
}
/* ... */
};
Note: You need the friend declaration because matrix<Type1, Dim1> and matrix<Type2, Dim2> are completely different types whenever Type1 != Type2 or Dim1 != Dim2 and as such, you cannot access matrix<OtherT, OtherDim>'s private/protected members in matrix<t, dim> without that friend declaration.
This implementation will initialize the target matrix by filling its data member with the content of the given matrix when the value types match:
If the given matrix is bigger, it will be truncated.
If the given matrix is smaller, the remaining elements will be 0 initialized
If the value types don't match, the less specialized matrix<OtherT, OtherDim> constructor is the only available overload and it triggers a compiler error through a static_assert.
You would also need to define the equivalent assigment operators... Which I left as exercises.
A demo of these constructors in action can be found on Coliru
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
Please consider the following (partial) implementation of a mathematical vector class (which is basically the code you can find in the Wikipedia article about expression templates):
namespace math
{
template<class E>
class vector_expression
{
public:
std::size_t size() const {
return static_cast<E const&>(*this).size();
}
double operator[](size_t i) const
{
if (i >= size())
throw std::length_error("");
return static_cast<E const&>(*this)[i];
}
operator E&() { return static_cast<E&>(*this); }
operator E const&() const { return static_cast<E const&>(*this); }
}; // class vector_expression
template<class E1, class E2>
class vector_sum
: public vector_expression<vector_sum<E1, E2>>
{
public:
vector_sum(vector_expression<E1> const& e1, vector_expression<E2> const& e2)
: m_e1(e1), m_e2(e2)
{
if (e1.size() != e2.size())
throw std::logic_error("");
}
std::size_t size() const {
return m_e1.size(); // == m_e2.size()
}
double operator[](std::size_t i) const {
return m_e1[i] + m_e2[i];
}
private:
E1 const& m_e1;
E2 const& m_e2;
}; // class vector_sum
template<typename E1, typename E2>
vector_sum<E1, E2> operator+(vector_expression<E1> const& e1, vector_expression<E2> const& e2) {
return { e1, e2 };
}
template<typename T>
class vector
: public vector_expression<vector<T>>
{
public:
vector(std::size_t d)
: m_data(d)
{ }
vector(std::initializer_list<T> init)
: m_data(init)
{ }
template<class E>
vector(vector_expression<E> const& expression)
: m_data(expression.size())
{
for (std::size_t i = 0; i < expression.size(); ++i)
m_data[i] = expression[i];
}
std::size_t size() const {
return m_data.size();
}
double operator[](size_t i) const { return m_data[i]; }
double& operator[](size_t i) { return m_data[i]; }
private:
std::vector<T> m_data;
}; // class vector
} // namespace math
How should I extend this implementation to allow the following operations:
vector<double> x = { ... };
auto y = 4711 * x; // or y = x * 4711
auto z = 1 + x; // or x + 1, which should yield z[i] = x[i] + 1
I suppose that I need something like
namespace math
{
template<class E, typename T>
class vector_product
: public vector_expression<vector_product<E, T>>
{
public:
vector_product(vector_expression<E> const& e, T const& t)
: m_e(e), m_t(t)
{ }
std::size_t size() const {
return m_e.size();
}
double operator[](std::size_t i) const {
return m_e[i] * m_t;
}
private:
E const& m_e;
T const& m_t;
}; // class vector_product
template<class E, typename T>
vector_product<E, T> operator*(vector_expression<E> const& e, T const& t) {
return { e, t };
}
template<class E, typename T>
vector_product<E, T> operator*(T const& t, vector_expression<E> const& e) {
return e * t;
}
} // namespace math
but I don't know whether or not this is a good approach. So, how should I do it? And should I add any copy or move constructor/assignment operator? I guess not, since the implicit ones should do a perfect job, cause the only member variable of vector is a STL type.
I would simply extend vector_sum to allow for E2 to be double, and to handle that gracefully if it is. This would involve taking arguments of E1 const& and E2 const& in your constructor, potentially not throwing on size differences (since scalars have no size), and rewriting operator[] to not do indexing. For the last part, something like:
double operator[](std::size_t i) const {
return m_e1[i] + get(m_e2, i);
}
private:
template <class E>
double get(E const& rhs, std::size_t i) const {
return rhs[i];
}
double get(double scalar, std::size_t ) const {
return scalar;
}
This way, if you're adding two vector_expressions, you'll do the indexing, but if you're adding a vector_expression and a double - you won't even try to index into the double. This switch happens at compile-time, so there's no run-time overhead.
Then, all you just need to add a couple more operator+s:
template <typename E1>
vector_sum<E1, double> operator+(vector_expression<E1> const& e1, double d) {
return {e1, d};
}
template <typename E1>
vector_sum<E1, double> operator+(double d, vector_expression<E1> const& e1) {
return {e1, d};
}
Which lets you write:
math::vector<int> x = {1, 2, 3, 4};
math::vector<int> y = {2, 3, 4, 5};
auto sum = 3 + x + 1;
Keeping references to const probably isn't the right thing to do - if you did a+b+c, you'll end up keeping a reference to the temporary a+b. You probably only want to keep a reference to the actual vector, and keep copies of all the intermediate objects.
To support vector_product, you'll probably want vector_sum<E1,E2> to really be vector_binary_op<E1,E2,std::plus<>> and then vector_product<E1,E2> should be vector_binary_op<E1,E2,std::multiplies<>>. That way, you won't have all the duplication.
Inspired by Yakk and Barry, I've finally come up with the following:
namespace math
{
template<class E>
class expression
{
public:
auto size() const {
return static_cast<E const&>(*this).size();
}
auto operator[](std::size_t i) const
{
if (i >= size())
throw std::length_error("");
return static_cast<E const&>(*this)[i];
}
operator E&() { return static_cast<E&>(*this); }
operator E const&() const { return static_cast<E const&>(*this); }
}; // class expression
template<typename T, class Allocator = std::allocator<T>>
class vector
: public expression<vector<T>>
{
private:
using data_type = std::vector<T, Allocator>;
data_type m_data;
public:
using value_type = T;
using allocator_type = Allocator;
using size_type = typename data_type::size_type;
using difference_type = typename data_type::difference_type;
using reference = typename data_type::reference;
using const_reference = typename data_type::const_reference;
using pointer = typename data_type::pointer ;
using const_pointer = typename data_type::const_pointer;
vector(size_type d)
: m_data(d)
{ }
vector(std::initializer_list<value_type> init)
: m_data(init)
{ }
template<class E>
vector(expression<E> const& expression)
: m_data(expression.size())
{
for (size_type i = 0; i < expression.size(); ++i)
m_data[i] = expression[i];
}
size_type size() const {
return m_data.size();
}
value_type operator[](size_type i) const { return m_data[i]; }
value_type& operator[](size_type i) { return m_data[i]; };
}; // class vector
namespace detail
{
template<typename T>
class scalar
: public expression<scalar<T>>
{
public:
using value_type = T;
using allocator_type = std::allocator<void>;
using size_type = typename std::allocator<T>::size_type;
using difference_type = typename std::allocator<T>::difference_type;
using reference = typename std::allocator<T>::reference;
using const_reference = typename std::allocator<T>::const_reference;
using pointer = typename std::allocator<T>::pointer;
using const_pointer = typename std::allocator<T>::const_pointer;
scalar(value_type value)
: m_value(value)
{ }
size_type size() const {
return 0;
}
operator value_type&() { return static_cast<value_type&>(*this); }
operator value_type const&() const { return static_cast<value_type const&>(*this); }
value_type operator[](size_type i) const { return m_value; }
value_type& operator[](size_type i) { return m_value; }
private:
value_type m_value;
}; // class scalar
template<class>
struct is_scalar : std::false_type { };
template<class T>
struct is_scalar<scalar<T>> : std::true_type { };
} // namespace detail
template<class E1, class E2, class BinaryOperation>
class vector_binary_operation
: public expression<vector_binary_operation<E1, E2, BinaryOperation>>
{
public:
using value_type = decltype(BinaryOperation()(typename E1::value_type(), typename E2::value_type()));
using allocator_type = std::conditional_t<
detail::is_scalar<E1>::value,
typename E2::allocator_type::template rebind<value_type>::other,
typename E1::allocator_type::template rebind<value_type>::other>;
private:
using vector_type = vector<value_type, allocator_type>;
public:
using size_type = typename vector_type::size_type;
using difference_type = typename vector_type::difference_type;
using reference = typename vector_type::reference;
using const_reference = typename vector_type::const_reference;
using pointer = typename vector_type::pointer;
using const_pointer = typename vector_type::const_pointer;
vector_binary_operation(expression<E1> const& e1, expression<E2> const& e2, BinaryOperation op)
: m_e1(e1), m_e2(e2),
m_op(op)
{
if (e1.size() > 0 && e2.size() > 0 && !(e1.size() == e2.size()))
throw std::logic_error("");
}
size_type size() const {
return m_e1.size(); // == m_e2.size()
}
value_type operator[](size_type i) const {
return m_op(m_e1[i], m_e2[i]);
}
private:
E1 m_e1;
E2 m_e2;
//E1 const& m_e1;
//E2 const& m_e2;
BinaryOperation m_op;
}; // class vector_binary_operation
template<class E1, class E2>
vector_binary_operation<E1, E2, std::plus<>>
operator+(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::plus<>() };
}
template<class E1, class E2>
vector_binary_operation<E1, E2, std::minus<>>
operator-(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::minus<>() };
}
template<class E1, class E2>
vector_binary_operation<E1, E2, std::multiplies<>>
operator*(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::multiplies<>() };
}
template<class E1, class E2>
vector_binary_operation<E1, E2, std::divides<>>
operator/(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::divides<>() };
}
template<class E, typename T>
vector_binary_operation<E, detail::scalar<T>, std::divides<>>
operator/(expression<E> const& expr, T val) {
return{ expr, detail::scalar<T>(val), std::divides<>() };
}
template<class E, typename T>
vector_binary_operation<E, detail::scalar<T>, std::multiplies<>>
operator*(T val, expression<E> const& expr) {
return{ expr, detail::scalar<T>(val), std::multiplies<>() };
}
template<class E, typename T>
vector_binary_operation<E, detail::scalar<T>, std::multiplies<>>
operator*(expression<E> const& expr, T val) {
return{ expr, detail::scalar<T>(val), std::multiplies<>() };
}
} // namespace math
This allows the operations +, -, /, * for two vectors of the same size as well as the multiplication a * x and x * a of a vector x and a value a. Moreover, we can divide x / a, but not a / x (since that makes no sense). I think that this is the most plausible solution.
However, there are still some issues: I've added an Allocator template parameter to the vector class. In vector_binary_operation I need to know the resulting vector type. It would be possible that both expressions have a different allocator_type. I've decided to choose the allocator_type of the first expression in vector_binary_operation. I don't think that this is a real problem, since I don't think that it would make much sense to use different Allocators in this scenario.
The bigger issue is that I don't know how I need to deal with the expression member variables in vector_binary_operation. It would make sense to declare them as const references, cause the whole point of the code is to avoid unnecessary copies. However, as Barry pointed out, if we write sum = a + b + c, we will end up keeping a reference to the temporary a + b. Doing sum[0] will call operator()[0] on that temporary. But that object was deleted after the previous line.
I don't know what I need to do here and asked another question.
Make a specializattion of template<class E> class vector_expression for E=double.
Add in concept of min/max size (as doubles are 0 to infinite dimension), and maybe is_scalar (might not be needed, except cast-to-scalar?).
Kill vector_sum and take its toys. Make binop_vector that takes a elementop on the elements.
Add
friend binop_vector<vector_expression,R,ElemAdd>
operator+( vector_expression const& l. R const& r ){
return {l,r};
}
friend binop_vector<vector_expression<double>,vector_expression,ElemAdd>
operator+( double const& l. vector_expression const const& r ){
return {l,r};
}
And similar for * with ElemMult to vector_expression. Ths binop uses the element wise operation to implement [].
Change asserts to ensure you have overlapping min/max sizes. Report intersection in binop_vector.
The vector_expression<double> overload has a min 0 max -1 (size_t) and always returns its value. If you need more than one scalar type, write scalar_expression<T> and inherit vector_expression<double> (etc) from it.
The above is not tested, just written on phone as I sit in garage.
I am currently writing library to deal with little math vectors and matrices and some special functions for my research domain. I am currently testing some CRTP tricks. The following code produces and error at the last line, and I don't know why :
#include <iostream>
#include <initializer_list>
#include <type_traits>
// Abstract class
template<class TCRTP, class T, unsigned int TSIZE> class AbstractArray
{
// Constructor
public:
inline AbstractArray() : _data{}
{
std::cout<<"AbstractArray::AbstractArray()"<<std::endl;
}
// Copy constructor
public:
template<class TCRTP0, class T0> inline AbstractArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)
{
std::cout<<"AbstractArray::AbstractArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)"<<std::endl;
for(unsigned int i = 0; i < TSIZE; ++i) {
_data[i] = rhs[i];
}
}
// Initializer list constructor
public:
template<class T0> inline AbstractArray(const std::initializer_list<T0>& rhs)
{
std::cout<<"AbstractArray::AbstractArray(const std::initializer_list<T0>& rhs)"<<std::endl;
const T0* it = rhs.begin();
for (unsigned int i = 0; i < TSIZE; ++i) {
_data[i] = *it;
++it;
}
}
// Destructor
public:
inline ~AbstractArray()
{
std::cout<<"AbstractArray::~AbstractArray()"<<std::endl;
}
// Subscript operator
public:
inline const T& operator[](const unsigned int i) const
{
std::cout<<"AbstractArray::operator[](const unsigned int i) const"<<std::endl;
return _data[i];
}
inline T& operator[](const unsigned int i)
{
std::cout<<"AbstractArray::operator[](const unsigned int i)"<<std::endl;
return _data[i];
}
// Assignment operator
public:
template<class TCRTP0, class T0> inline AbstractArray<TCRTP, T, TSIZE>& operator=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)
{
std::cout<<"AbstractArray::operator=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)"<<std::endl;
for (unsigned int i = 0; i < TSIZE; ++i) {
_data[i] = rhs[i];
}
return *this;
}
// Sum assignment
public:
template<class TCRTP0, class T0> inline AbstractArray<TCRTP, T, TSIZE>& operator+=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)
{
std::cout<<"AbstractArray::operator+=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)"<<std::endl;
for (unsigned int i = 0; i < TSIZE; ++i) {
_data[i] += rhs[i];
}
return *this;
}
// Sum operator
public:
template<class T0> inline AbstractArray<TCRTP, typename std::common_type<T, T0>::type, TSIZE> operator+(const AbstractArray<TCRTP, T0, TSIZE>& rhs) const
{
return AbstractArray<TCRTP, typename std::common_type<T, T0>::type, TSIZE>(*this) += rhs;
}
// Data members
protected:
T _data[TSIZE];
};
// Array class
template<class T, unsigned int TSIZE> class NArray : public AbstractArray<NArray<T, TSIZE>, T, TSIZE>
{
// Constructor
public:
inline NArray() : AbstractArray<NArray<T, TSIZE>, T, TSIZE>()
{
std::cout<<"NArray::NArray()"<<std::endl;
}
// Copy constructor
public:
template<class TCRTP0, class T0> inline NArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs) : AbstractArray<NArray<T, TSIZE>, T, TSIZE>(rhs)
{
std::cout<<"NArray::NArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)"<<std::endl;
}
// Initializer list constructor
public:
template<class T0> inline NArray(const std::initializer_list<T0>& rhs) : AbstractArray<NArray<T, TSIZE>, T, TSIZE>(rhs)
{
std::cout<<"NArray::NArray(const std::initializer_list<T0>& rhs)"<<std::endl;
}
// Destructor
public:
inline ~NArray()
{
std::cout<<"NArray::~NArray()"<<std::endl;
}
};
// Vector class
template<class T, unsigned int TSIZE> class NVector : public AbstractArray<NVector<T, TSIZE>, T, TSIZE>
{
// Constructor
public:
inline NVector() : AbstractArray<NVector<T, TSIZE>, T, TSIZE>()
{
std::cout<<"NVector::NVector()"<<std::endl;
}
// Copy constructor
public:
template<class TCRTP0, class T0> inline NVector(const AbstractArray<TCRTP0, T0, TSIZE> &rhs) : AbstractArray<NVector<T, TSIZE>, T, TSIZE>(rhs)
{
std::cout<<"NVector::NVector(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)"<<std::endl;
}
// Initializer list constructor
public:
template<class T0> inline NVector(const std::initializer_list<T0>& rhs) : AbstractArray<NVector<T, TSIZE>, T, TSIZE>(rhs)
{
std::cout<<"NVector::NVector(const std::initializer_list<T0>& rhs)"<<std::endl;
}
// Destructor
public:
inline ~NVector()
{
std::cout<<"NVector::~NVector()"<<std::endl;
}
};
// Main
int main()
{
NArray<double, 3> a1({1., 2., 3.});
std::cout<<std::endl;
NArray<int, 3> a2({4., 5., 6.});
std::cout<<std::endl;
NArray<double, 3> a3({7., 8., 9.});
std::cout<<std::endl;
NVector<double, 3> v1({11., 12., 13.});
std::cout<<std::endl;
NVector<double, 3> v2({14., 15., 16.});
std::cout<<std::endl;
NVector<double, 3> v3({17., 18., 19.});
std::cout<<std::endl;
NVector<int, 3> v4({20., 21., 22.});
std::cout<<std::endl;
a1 = a2;
std::cout<<std::endl;
std::cout<<"TEST -> a1 = "<<a1[0]<<" "<<a1[1]<<" "<<a1[2]<<std::endl;
std::cout<<std::endl;
v1 = a2;
std::cout<<std::endl;
std::cout<<"TEST -> v1 = "<<v1[0]<<" "<<v1[1]<<" "<<v1[2]<<std::endl;
std::cout<<std::endl;
v1 += a2;
std::cout<<std::endl;
std::cout<<"TEST -> v1 = "<<v1[0]<<" "<<v1[1]<<" "<<v1[2]<<std::endl;
std::cout<<std::endl;
v1 = a3+a3;
std::cout<<std::endl;
std::cout<<"TEST -> v1 = "<<v1[0]<<" "<<v1[1]<<" "<<v1[2]<<std::endl;
std::cout<<std::endl;
//v2 = v3+v4; // <- This line does not work : "error : no match for "operator+" in "v3+v4"
std::cout<<std::endl;
return 0;
}
How to solve the problem ?
And furthemore a question for experts : do you think that this way of coding operators is efficient, or do you have in mind some modifications that can improve the quality of the code ?
Any advice will be appreciated before I start to modify my current implementation with CRTP.
Thank you very much !
You can fix make this compile this by using:
v2 = v3.operator+<int> (v4);
instead of
v2 = v3+v4; // <- This line does not work : "error : no match for
I have explicitly told the compiler what that T0 is int. I have done this by using .operator+<int>.
But, there may be another subtle problem. In the definition of operator+, we see that it takes a const AbstractArray<TCRTP, T0, TSIZE>& rhs as argument. In this case T0 == int and TSIZE == 3, which is good; but the problem is that TCRTP is still defined to be NVector<double,3> when perhaps it should be NVector<int,3>?
In summary, it is possible to explicitly tell the compiler the template parameters. But that raises two questions:
Even if you manually specify the types, are they the correct types? Should you have a distinct TCRTP and TCRTP0 as is used in operator+=
How do we automatically infer the (correct) types?