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?
Related
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'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 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
I am working on operator overloading to generate lazy object evaluation. For this reason class at operator+() doesn’t do more than storing reference of passed classes to evaluate later.
struct Base
{
virtual void expensive_func()
{
throw "cant use this";
};
Composer operator+(int i)
{
return Composer(*this, i);
}
}
struct Composer:public Base
{
Base& refBase;
int increment;
virtual void expensive_func()
{
heavy_work();
};
Composer(Base& a,int inc):
refBase(a),increment(inc)
{
}
}
struct D:public Base
{
...
}
And than problem arase
D a;
auto b = a + 2;
auto c = b + 3;
auto e = a + 3 + 4;
a.expensive_func(); //fine
b.expensive_func(); //fine
c.expensive_func(); //fine
e.expensive_func(); //segfault
On solution is to prevent such manoeuvres with
operator+(const Coposer&&,int) = delete;
But this just prevents doing something of what I would like to do
Full code: - I am building with gcc/g++ 4.8
#include <iostream>
#include <string.h>
#include <vector>
#include <algorithm>
#include <memory>
namespace Intervals
{
template <typename T> struct ContainerMove; //forward declaration
template <typename T>
struct ContainerBase {
typedef T mT;
virtual T GetInterval (const T& val)
{
throw; //overload this
}
T Integrate(const T& start = T(0))
{
T r(0);
T next(start);
while(1)
{
T i = GetInterval(next);
if(i<0) //less that 0 is considered end
{
return r;
}
r+=i;
next=i;
}
}
ContainerMove<T> operator +(const T& left);
ContainerMove<T> operator -(const T& left);
virtual ~ContainerBase(){};
};
//lazy container of ContainerBase
template <typename T>
struct ContainerMove:public ContainerBase<T>
{
typedef ContainerBase<T> mConatinerBase;
const T mOffset;
mConatinerBase& mIntervalSet;
ContainerMove(mConatinerBase& intervalset, const T& offset)
:mOffset(offset),mIntervalSet(intervalset)
{}
virtual T GetInterval (const T& val)
{
auto i = mIntervalSet.GetInterval(val-mOffset);
if(i < 0)
{
return T(-1000);
}
return T(i+mOffset);
}
};
template <typename T>
ContainerMove<T> ContainerBase<T>::operator +(const ContainerBase<T>::mT& a)
{
return ContainerMove<T>(*this,a);
}
template <typename T>
ContainerMove<T> ContainerBase<T>::operator -(const ContainerBase<T>::mT& a)
{
return ContainerMove<T>(*this,-a);
}
/*
template <typename T>
ContainerMove<T> operator +(const ContainerMove<T>&& , const typename ContainerBase<T>::mT&) = delete;
template <typename T>
ContainerMove<T> operator -(const ContainerMove<T>&& , const typename ContainerBase<T>::mT&) = delete;
*/
template <class T>
struct Container:public ContainerBase<T>
{
typedef Container<T> mThisType;
typedef T mT;
typedef std::vector<T> SortedContainer_t;
SortedContainer_t mContainer;
template<class ForwardIter>
Container(ForwardIter begin,ForwardIter end)
:mContainer(begin,end)
{
}
T GetInterval (const T& val)
{
auto r = std::upper_bound(mContainer.begin(), mContainer.end(),val);
if (r == mContainer.end())
{
return T(-1000); //just as exeample <0 is ivalid value
}
return *r;
}
};
}
int main()
{
typedef float T;
typedef Intervals::Container<T> ContainerType;
std::vector<T> v,u;
const int N = 10;
for(int i=0;i<N;++i)
{
v.push_back(T(i));
}
auto c = ContainerType(v.begin(),v.end());
auto d=c+T(1);
auto e=c+T(2)+T(3); //this yelds segmentation after e.Integrate()
//std::cout << c.Integrate() << std::endl;
//std::cout << d.Integrate() << std::endl;
std::cout << e.Integrate() << std::endl; //crash here
return 0;
}