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
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 am currently working on a c++ project and now i am stuck already for a while. It's about delayed evaluation with expression templates and (for me at least) a strange bad_alloc.
If you try the code below, you'll notice runtime error bad_alloc due to the very last addition b+c. So thats the point where the delayed evaluation is done. Furthermore the code below compiles and runs fine if you remove the references of the members of "Expression" (left,right). But i need references there, due to performance, etc. . However i also dont see, why i cant use references there.
I've already spent a lot of time with it. Please let me know if somebody can help me.
Best Regards.
#include <iostream>
#include <vector>
template<typename value_t, typename left_t, typename right_t, typename op_t>
class Expression
{
public:
typedef value_t value_type;
explicit Expression(const left_t &left,
const right_t &right,
const op_t &op) :
left(left),
right(right),
op(op)
{
}
value_t operator [](const size_t &i) const
{
return op(left[i],right[i]);
}
size_t size() const { return left.size();}
private:
const left_t &left;
const right_t &right;
//const left_t left;
//const right_t right;
const op_t &op;
};
template<class left_t,
class right_t,
class value_t = typename left_t::value_type,
class op_t = std::plus<value_t>>
const Expression<value_t, left_t, right_t, op_t> operator +(const left_t &left,
const right_t &right)
{
return Expression<value_t,left_t,right_t,op_t>(left, right, op_t());
}
template<typename value_t, typename data_t = std::vector<value_t>>
class Vector : public data_t
{
public:
typedef value_t value_type;
using data_t::size;
Vector(const std::initializer_list<value_t> &list) :
data_t(list)
{
}
Vector(const size_t &n) :
data_t(n)
{
}
Vector(const Vector &v) :
data_t(v)
{
}
template<typename left_t, typename right_t, typename op_t>
Vector(const Expression<value_t,left_t,right_t,op_t> &v) :
data_t(v.size())
{
operator =(v);
}
template<typename vec_t>
Vector(const vec_t &v) :
data_t(v.size())
{
operator =(v);
}
template<typename vec_t>
Vector &operator =(const vec_t &v)
{
for(size_t i = 0; i < data_t::size(); ++i)
data_t::operator [](i) = v[i];
return (*this);
}
friend std::ostream &operator <<(std::ostream &os, const Vector &v)
{
if(v.size())
os << v[0];
for(size_t i = 1; i < v.size(); ++i)
os << " " << v[i];
return os;
}
};
int main()
{
Vector<double> a{0,1,2};
auto b = a+a+a;
auto c = a;
std::cout << a+a+a+a << std::endl;
std::cout << b+c << std::endl; // gives bad_alloc
return 0;
}
"But i need references there, due to performance, etc."
Prove it.
In expression templates, all¹ the information should be compile-time.
You can see my example here for a simple expression template:
// we have lazy placeholder types:
template <int N> struct placeholder {};
placeholder<1> _1;
placeholder<2> _2;
placeholder<3> _3;
// note that every type here is stateless, and acts just like a more
// complicated placeholder.
// We can have expressions, like binary addition:
template <typename L, typename R> struct addition { };
template <typename L, typename R> struct multiplication { };
// here is the "factory" for our expression template:
template <typename L, typename R> addition<L,R> operator+(L const&, R const&) { return {}; }
template <typename L, typename R> multiplication<L,R> operator*(L const&, R const&) { return {}; }
///////////////////////////////////////////////
// To evaluate/interpret the expressions, we have to define "evaluation" for each type of placeholder:
template <typename Ctx, int N>
auto eval(Ctx& ctx, placeholder<N>) { return ctx.arg(N); }
template <typename Ctx, typename L, typename R>
auto eval(Ctx& ctx, addition<L, R>) { return eval(ctx, L{}) + eval(ctx, R{}); }
template <typename Ctx, typename L, typename R>
auto eval(Ctx& ctx, multiplication<L, R>) { return eval(ctx, L{}) * eval(ctx, R{}); }
///////////////////////////////////////////////
// A simple real-life context would contain the arguments:
#include <vector>
struct Context {
std::vector<double> _args;
// define the operation to get an argument from this context:
double arg(int i) const { return _args.at(i-1); }
};
#include <iostream>
int main() {
auto foo = _1 + _2 + _3;
Context ctx { { 3, 10, -4 } };
std::cout << "foo: " << eval(ctx, foo) << "\n";
std::cout << "_1 + _2 * _3: " << eval(ctx, _1 + _2 * _3) << "\n";
}
So what you need is a literal type that holds a reference to the associated value, and defer all other evaluation to evaluation time.
I might prefer to add the size() operation as a free function, so that you don't have to encumber all the expression types with it (Separation Of Concerns).
¹ nearly, nl. except when encoding literals
Proof Of Concept
Using the strategy outlined:
Live On Coliru
#include <iostream>
#include <tuple>
namespace ETL {
template <typename T>
struct Literal {
T value;
T get() const { return value; }
};
/*
*template <typename T>
* static inline std::ostream& operator<<(std::ostream& os, ETL::Literal<T> const& lit) {
* return os << __PRETTY_FUNCTION__ << "\n actual: lit.value = " << lit.value;
* }
*/
template <class L, class R, class Op>
struct BinaryExpr : std::tuple<L, R, Op> { // tuple optimizes for empty element types
BinaryExpr(L l, R r, Op op)
: std::tuple<L, R, Op> { l, r, op }
{}
L const& get_lhs() const { return std::get<0>(*this); }
R const& get_rhs() const { return std::get<1>(*this); }
Op const& get_op() const { return std::get<2>(*this); }
};
template <class L, class R, class Op> auto cured(BinaryExpr<L,R,Op> _) { return _; }
template <class T> auto cured(Literal<T> l) { return std::move(l); }
template <class T> Literal<T> cured(T&& v) { return {std::forward<T>(v)}; }
template <class Op, class L, class R>
BinaryExpr<L, R, Op> make_binexpr(L&& l, R&& r) { return { std::forward<L>(l), std::forward<R>(r), Op{} }; }
template <class L, class R> auto operator +(L&& l, R&& r)
{ return make_binexpr<std::plus<>>(cured(std::forward<L>(l)), cured(std::forward<R>(r))); }
template <class L, class R> auto operator -(L&& l, R&& r)
{ return make_binexpr<std::minus<>>(cured(std::forward<L>(l)), cured(std::forward<R>(r))); }
template <class L, class R> auto operator *(L&& l, R&& r)
{ return make_binexpr<std::multiplies<>>(cured(std::forward<L>(l)), cured(std::forward<R>(r))); }
template <class L, class R> auto operator /(L&& l, R&& r)
{ return make_binexpr<std::divides<>>(cured(std::forward<L>(l)), cured(std::forward<R>(r))); }
template <class L, class R> auto operator %(L&& l, R&& r)
{ return make_binexpr<std::modulus<>>(cured(std::forward<L>(l)), std::forward<R>(r)); }
template <typename T> auto val(T const& v)
{ return cured(v); }
namespace impl {
template <class T>
static constexpr auto is_indexable(T const&) -> decltype(std::declval<T const&>()[0], std::true_type{}) { return {}; }
static constexpr auto is_indexable(...) -> decltype(std::false_type{}) { return {}; }
struct {
template <class T> size_t operator()(T const& v) const { return (*this)(v, is_indexable(v)); }
template <class T> size_t operator()(T const& v, std::true_type) const { return v.size(); }
template <class T> size_t operator()(T const&, std::false_type) const { return 0; }
template <class T> size_t operator()(Literal<T> const& l) const { return (*this)(l.value); }
template <class L, class R, class Op>
size_t operator()(BinaryExpr<L,R,Op> const& be) const { return (*this)(be.get_lhs()); }
} size;
struct {
template <class T>
auto operator()(size_t i, T const& v) const { return (*this)(i, v, is_indexable(v)); }
template <class T>
auto operator()(size_t i, T const& v, std::true_type) const { return v[i]; }
template <class T>
auto operator()(size_t, T const& v, std::false_type) const { return v; }
template <class T> auto operator()(size_t i, Literal<T> const& l) const { return (*this)(i, l.value); }
template <class L, class R, class Op>
auto operator()(size_t i, BinaryExpr<L,R,Op> const& be) const {
return be.get_op()((*this)(i, be.get_lhs()), (*this)(i, be.get_rhs()));
}
} eval_at;
}
template <typename T> size_t size(T const& v) { return impl::size(v); }
template <typename T> auto eval_at(size_t i, T const& v) { return impl::eval_at(i, v); }
}
#include <vector>
template <class value_t>
struct Vector : std::vector<value_t> {
using data_t = std::vector<value_t>;
typedef value_t value_type;
using data_t::data_t;
template <typename Expr>
Vector(Expr const& expr) { *this = expr; }
template <typename Expr>
Vector& operator=(Expr const& expr) {
this->resize(size(expr));
for (size_t i = 0; i < this->size(); ++i)
this->at(i) = eval_at(i, expr);
return *this;
}
friend std::ostream &operator<<(std::ostream &os, const Vector &v) {
for (auto& el : v) os << " " << el;
return os;
}
};
int main() {
Vector<double> a { 1, 2, 3 };
using ETL::operator+;
using ETL::operator*;
//std::cout << typeid(a + a * 4 / 2).name() << "\n";
#define DD(x) std::cout << typeid(x).name() << " size: " << ETL::size(x) << " result:" << (x) << "\n"
DD(a * -100.0);
auto b = a + a + a;
auto c = a;
std::cout << size(b) << "\n";
std::cout << (a + a + a + a) << "\n";
std::cout << a * 4.0 << "\n";
std::cout << b + c << "\n";
std::cout << (a + a + a + a) - 4 * a << "\n";
}
Prints
ETL::BinaryExpr<ETL::Literal<Vector<double>&>, ETL::Literal<double>, std::multiplies<void> > size: 3 result: -100 -200 -300
3
4 8 12
4 8 12
4 8 12
0 0 0
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.
Here is the code:
template <typename L, typename R> bool eq (const L& lhs, const R& rhs) { return lhs == rhs; }
template<int N> bool eq(char* lhs, const char(&rhs)[N]) { return String(lhs).compare(rhs) == 0; }
template<int N> bool eq(const char(&lhs)[N], char* rhs) { return String(lhs).compare(rhs) == 0; }
inline bool eq(char* lhs, char* rhs) { return String(lhs).compare(rhs) == 0; }
inline bool eq(const char* lhs, const char* rhs) { return String(lhs).compare(rhs) == 0; }
inline bool eq(char* lhs, const char* rhs) { return String(lhs).compare(rhs) == 0; }
inline bool eq(const char* lhs, char* rhs) { return String(lhs).compare(rhs) == 0; }
I have to do this for neq/lt/gt/lte/gte and not just for equality. Maybe I've already missed something.
Is there a way to not list all the possible combinations of C string types?
Also C++98.
EDIT: >> here << is an online demo with the problem
Decay an array type to pointer:
template<class T>
struct decay_array { typedef T type; };
template<class T, size_t N>
struct decay_array<T[N]> { typedef T* type; };
template<class T>
struct decay_array<T[]> { typedef T* type; };
Check that a type is not a pointer to (possibly const) char:
template<class T>
struct not_char_pointer { enum { value = true }; };
template<>
struct not_char_pointer<char*> { enum { value = false }; };
template<>
struct not_char_pointer<const char*> { enum { value = false }; };
Now check that a type is not a pointer to or array of (possibly const) char:
template<class T>
struct can_use_op : not_char_pointer<typename decay_array<T>::type> {};
Reimplement std::enable_if:
template<bool, class = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
and use it to constrain your template:
template <typename L, typename R>
typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type
eq (const L& lhs, const R& rhs) { return lhs == rhs; }
Then just one overload is enough:
inline bool eq(const char* lhs, const char* rhs) { return String(lhs).compare(rhs) == 0; }
namespace details{
template<template<class...>class Z,class,class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z,class...Ts>
struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
}
template<template<class...>class Z,class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
This tests if a template can be applied some types.
namespace strcmp{
bool eq(const char*lhs, const char*rhs){/* body */}
}
template<class L, class R>
using str_eq_r=decltype(strcmp::eq(std::declval<L>(),std::declval<R>()));
template<class L, class R>
using can_str_eq=can_apply<str_eq_r,L,R>;
can_str_eq is truthy iff we can call stdcmp::eq on it.
namespace details {
bool eq(const char* lhs, const char* rhs, std::true_type){
return strcmp::eq(lhs,rhs);
}
template<class L,class R>
bool eq(L const& l, R const&r,std::false_type){
return l==r;
}
}
template<class L,class R>
bool eq(L const& l, R const&r){
return details::eq(l,r,can_str_eq<L const&,R const&>{});;
}
We could also use a static_if trick to do it inline, if you like:
template<class L,class R>
bool eq(L const& l, R const&r){
return static_if<can_str_eq>( l, r )(
strcmp::eq,
[](auto&& l, auto&& r){return l==r;}
);
}
After writing a static_if:
template<class...Ts>
auto functor(Ts...ts){
return [=](auto&& f){
return f(ts...);
};
}
namespace details{
template<class Functor>
auto switcher(std::true_type, Functor functor){
return [=](auto&& t, auto&&){
return functor(t);
};
}
template<class Functor>
auto switcher(std::false_type, Functor functor){
return [=](auto&&, auto&& f){
return functor(f);
};
}
}
template<template<class...>class test, class...Ts>
auto static_if(Ts...ts){
return details::switcher(
test<Ts...>{},
functor(ts...)
);
}
now, what are the odds that works? (Written on phone, not compiled yet) Also not optimal: lots of perfect forwarding, some of which requires de-lamdaing, required.
I'm rewriting the vector math portion of my project, and I'd like to generalize vectors by their type and number of dimensions. A vector<T, N> represents an N dimensional vector of type T.
template<typename T, int N>
struct vector {
T data[N];
};
I'll need to rewrite many math functions, most of which will operate on a per-component basis. The straightforward implementation of the addition operator is shown below.
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
for (int i = 0; i < N; i++) {
result[i] = lhs[i] + rhs[i];
}
return result;
}
My question: Is there a way (via template-trickery?) to implement this without the use of a for loop and a temporary variable? I understand that the compiler would most likely unroll the loop and optimize it away. I just don't like the idea of having all of my performance-critical math functions implemented this way. They will all be inlined and in the header, so having many of these functions would also make for a big ugly header file.
I'm wondering if there is a way to do this which would produce more optimal source code. Possibly a way that works like variadic templates do. Something along the lines of this.
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
return vector<T, N>(lhs[0] + rhs[0], lhs[1] + rhs[1]...);
}
One way to do this is via lower level "map" functions:
Here's a complete working example
#include <iostream>
#include <math.h>
template<typename T, int N>
struct vector {
T data[N];
};
First declare your worker "map" functions - I've got 3 here map, map2, foreach.
template<typename T, int N, typename FN>
static void foreach(const vector<T,N> & vec, FN f) {
for(int i=0; i<N ;++i) {
f(vec.data[i]);
}
}
template<typename T, int N, typename FN>
static auto map(const vector<T,N> & vec, FN f) -> vector<decltype(f(T(0))), N> {
vector<decltype(f(T(0))), N> result;
for(int i=0; i<N ;++i) {
result.data[i] = f(vec.data[i]);
}
return result;
}
template<typename T1, typename T2, int N, typename FN>
static auto map2(const vector<T1,N> & vecA,
const vector<T2,N> & vecB,
FN f)
-> vector<decltype(f(T1(0), T2(0))), N> {
vector<decltype(f(T1(0), T2(0))), N> result;
for(int i=0; i<N ;++i) {
result.data[i] = f(vecA.data[i], vecB.data[i]);
}
return result;
}
Now use the helpers to define your higher level functions via lambdas. I'll define binary +, binary -, unary - and e^x. Oh and operator<< so we can see what is going on.
I'm pretty sure there's a better alternative to the lambdas used in operator+ and operator-, but I can't remember them
template<typename T, int N>
vector<T,N> operator+(const vector<T,N> &lhs, const vector<T,N> &rhs) {
return map2(lhs, rhs, [](T a,T b) { return a+b;} );
}
template<typename T, int N>
vector<T,N> operator-(const vector<T,N> &lhs, const vector<T,N> &rhs) {
return map2(lhs, rhs, [](T a,T b) { return a-b;} );
}
template<typename T, int N>
vector<T,N> operator-(const vector<T,N> &vec) {
return map(vec, [](T a) { return -a;} );
}
template<typename T, int N>
auto exp(const vector<T,N> &vec) -> vector<decltype(exp(T(0))), N> {
return map(vec, [](T a) { return exp(a); } );
}
template<typename T, int N>
std::ostream & operator<<(std::ostream& os, const vector<T,N> &vec) {
os<<"{";
foreach(vec, [&os](T v) { os<<v<<", "; } );
os<<"}";
return os;
}
Now look how they work just fine...
int main() {
vector<int, 5> v1 = {1,2,3,4,5};
vector<int, 5> v2 = {2,4,6,8,10};
std::cout<<v1 << " + " << v2 << " = " << v1+v2<<std::endl;
std::cout<<v1 << " - " << v2 << " = " << v1-v2<<std::endl;
std::cout<<" exp( - " << v2 << " )= " << exp(-v1)<<std::endl;
}
You can do this and I'll point you towards a solution (which compiles and runs). You are looking to get rid of the loop, preferably by inlining it in hopes the compiler will optimize things for you.
In practice I have found it sufficient to specify the dimensions needed, i.e. N = 3, 4, 5 because this allows finer grained control over what the compiler does than doing what you asked for. However you can use recursion and partial template specialization to implement your operators. I have illustrated addition.
So instead of this:
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
for (int i = 0; i < N; i++) {
result[i] = lhs[i] + rhs[i];
}
return result;
}
You want code that effectively does this:
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
result[0] = lhs[0] + rhs[0];
result[1] = lhs[1] + rhs[1];
...
result[N-1] = lhs[N-1] + rhs[N-1];
return result;
}
if N is 1, it is pretty easy you just want this...
template
vector operator+(vector lhs, vector rhs) {
vector result;
result[0] = lhs[0] + rhs[0];
return result;
}
and if N is 2, it is pretty easy you just want this...
template
vector operator+(vector lhs, vector rhs) {
vector result;
result[0] = lhs[0] + rhs[0];
result[1] = lhs[1] + rhs[1];
return result;
}
The easiest way is to simply define this up to as many N as you expect to use and not the answer you are looking for because you probably don't need more than N=5 or N=6 in practice right?
However, you can also use partial template specialization and recursion to get there. Consider this struct, which recursively calls itself and then assigns the index:
template<typename T, int N, int IDX>
struct Plus
{
void operator()(vector<T,N>& lhs, vector<T,N>& rhs, vector<T,N>& result)
{
Plus<T,N,IDX-1>()(lhs,rhs,result);
result.data[IDX] = lhs.data[IDX] + rhs.data[IDX];
}
};
and this partial specialization which appears to do nothing, but handles the case when the index is 0 and ends the recursion:
template<typename T, int N>
struct Plus<T,N,-1>
{
void operator()(vector<T,N>& lhs, vector<T,N>& rhs, vector<T,N>& result)
{
//noop
}
};
and finally this implementation of operator+ which instantiates Plus and calls it:
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
Plus<T,N,N-1>()(lhs,rhs,result);
return result;
}
You'll need to turn this into an operator to make it more general purpose but you get the idea. However this is mean to the compiler and it may take awhile in big projects even if it is super cool. In practice I have found that hand typing the overloads you want or writing script code to generate the C++ results in a more debuggable experience and code that in the end is simpler to read and easier for the compiler to optimize. More specifically if you write a script to generate the C++ you can include the SIMD intrinsics in the first place and not leave things to chance.
Firstly, compiler would probably unroll the loop.
Secondly, for better performance, pass your argument by const reference instead of by value to avoid extra copies.
And to answer your question, you may use std::index_sequence to construct in place, something like:
namespace detail
{
template<typename T, int N, std::size_t...Is>
vector<T, N> add(std::index_sequence<Is...>,
const vector<T, N>& lhs,
const vector<T, N>& rhs)
{
return {{ (lhs[Is] + rhs[Is])... }};
}
}
template<typename T, int N>
vector<T, N> operator+(const vector<T, N>& lhs, const vector<T, N>& rhs) {
return detail::add(std::make_index_sequence<N>{}, lhs, rhs);
}
Demo