Just made a simple Vec
class, nothing fancy but I am open to suggestions for improvements. Of course, this is not suppose to replace or used for the same tasks as std::vector
. This is more like opencv's Vec
and eventually I want to include this in a bigger project of mine.
#include <cmath>
#include <vector>
#include <initializer_list>
#include <cassert>
#define VEC_ASSERT(x) assert(x)
template<typename T, unsigned int C>
class Vec {
public:
typedef T dataType;
typedef T& dataType_ref;
typedef const T& dataType_cref;
// Empty Constructor
Vec();
// Single-Arg Constructor
explicit Vec(T v);
// From std::vector Constructor
Vec(const std::vector<T>& v);
// From std::initializer_list Constructor
Vec(const std::initializer_list<T>& l);
// Main Constructor
template<typename ... Args>
explicit Vec(T v, Args&& ... args);
// Get vector dimensions
unsigned int dim() const;
// Get vector length
double length() const;
// Get vectors dist
double dist(const Vec<T, C>& v) const;
// Get the cross product (3D Vectors only)
Vec<T, C> cross(const Vec<T, C>& v) const;
// Get the dot product
double dot(const Vec<T, C>& v) const;
// Get ortho vector (2D vectors only)
Vec<T, C> ortho() const;
// Normalize vector values
Vec<T, C> norm() const;
// Rotate (2D Vectors only)
Vec<T, C> rotate(double angle) const;
// Rotate on x-axis (3D Vectors only)
Vec<T, C> rotateX(double angle) const;
// Rotate on y-axis (3D Vectors only)
Vec<T, C> rotateY(double angle) const;
// Rotate on z-axis (3D Vectors only)
Vec<T, C> rotateZ(double angle) const;
// Convert to std::vector
std::vector<dataType> to_std_vector() const;
// Cast
template<typename TT, unsigned int CC = C>
Vec<TT, CC> to() const;
// Access vector values
dataType_ref operator[](int index);
dataType_ref operator()(int index);
dataType_cref operator[](int index) const;
dataType_cref operator()(int index) const;
// Vector Operations with Scalars
Vec<T, C> operator+(T v);
Vec<T, C> operator-(T v);
Vec<T, C> operator*(T v);
Vec<T, C> operator/(T v);
Vec<T, C>& operator+=(T v);
Vec<T, C>& operator-=(T v);
Vec<T, C>& operator*=(T v);
Vec<T, C>& operator/=(T v);
// Vector Operations with Vectors
Vec<T, C> operator+(const Vec<T, C>& v);
Vec<T, C> operator-(const Vec<T, C>& v);
Vec<T, C> operator*(const Vec<T, C>& v);
Vec<T, C> operator/(const Vec<T, C>& v);
private:
// Recursive pusher (used by constructor)
template<typename ... Args>
void push(T v, Args&& ... args);
// Base pusher
void push(T v);
// Vector values
dataType values[C];
// Index for Vector pusher
unsigned int idx;
};
template<typename T, unsigned int C>
Vec<T, C>::Vec() {
for ( unsigned int i = 0; i < C; ++i )
this->values[i] = 0;
}
template<typename T, unsigned int C>
Vec<T, C>::Vec(T v) {
for ( unsigned int i = 0; i < C; ++i )
this->values[i] = v;
}
template<typename T, unsigned int C>
Vec<T, C>::Vec(const std::vector<T>& v) {
VEC_ASSERT(v.size() <= C);
for ( unsigned i = 0; i < v.size(); ++i )
this->values[i] = v[i];
}
template<typename T, unsigned int C>
Vec<T, C>::Vec(const std::initializer_list<T>& l) {
VEC_ASSERT(l.size() <= C);
unsigned i = 0;
for ( auto it : l )
this->values[i++] = it;
}
template<typename T, unsigned int C>
template<typename ... Args>
Vec<T, C>::Vec(T v, Args&& ... args) {
this->idx = 0;
this->values[idx] = v;
this->push(args ...);
}
template<typename T, unsigned int C>
template<typename ... Args>
void Vec<T, C>::push(T v, Args&& ... args) {
this->values[++(this->idx)] = v;
this->push(args ...);
}
template<typename T, unsigned int C>
void Vec<T, C>::push(T v) {
VEC_ASSERT(this->idx + 1 < C);
this->values[++(this->idx)] = v;
}
template<typename T, unsigned int C>
unsigned int Vec<T, C>::dim() const {
return C;
}
template<typename T, unsigned int C>
double Vec<T, C>::length() const {
double result = 0;
for ( unsigned int i = 0; i < C; ++i )
result += this->values[i] * this->values[i];
return std::sqrt(result);
}
template<typename T, unsigned int C>
double Vec<T, C>::dist(const Vec<T, C>& v) const {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] - v[i];
return result.length();
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::cross(const Vec<T, C>& v) const {
VEC_ASSERT(C == 3);
Vec<T, C> result;
result[0] = this->values[1] * v[2] - this->values[2] * v[1];
result[1] = this->values[0] * v[2] - this->values[2] * v[0];
result[2] = this->values[0] * v[0] - this->values[1] * v[0];
return result;
}
template<typename T, unsigned int C>
double Vec<T, C>::dot(const Vec<T, C>& v) const {
double result = 0.0;
for ( unsigned int i = 0; i < C; ++i )
result += this->values[i] * v[i];
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::ortho() const {
VEC_ASSERT(C == 2);
return Vec<T, C>(this->values[1], -(this->values[0]));
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::norm() const {
VEC_ASSERT(this->length() != 0);
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] * (1.0 / this->length());
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::rotate(double angle) const {
VEC_ASSERT(C == 2);
double theta = angle / 180.0 * M_PI;
double c = std::cos(theta);
double s = std::sin(theta);
double x = this->values[0] * c - this->values[1] * s;
double y = this->values[0] * s + this->values[1] * c;
return Vec<T, C>(x, y);
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::rotateX(double angle) const {
VEC_ASSERT(C == 3);
double theta = angle / 180.0 * M_PI;
double c = std::cos(theta);
double s = std::sin(theta);
double x = this->values[0];
double y = this->values[1] * c - this->values[2] * s;
double z = this->values[1] * s + this->values[2] * c;
return Vec<T, C>(x, y, z);
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::rotateY(double angle) const {
VEC_ASSERT(C == 3);
double theta = angle / 180.0 * M_PI;
double c = std::cos(theta);
double s = std::sin(theta);
double x = this->values[0] * c + this->values[2] * s;
double y = this->values[1];
double z = -(this->values[0]) * s + this->values[2] * c;
return Vec<T, C>(x, y, z);
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::rotateZ(double angle) const {
VEC_ASSERT(C == 3);
double theta = angle / 180.0 * M_PI;
double c = std::cos(theta);
double s = std::sin(theta);
double x = this->values[0] * c - this->values[1] * s;
double y = this->values[0] * s + this->values[1] * c;
double z = this->values[2];
return Vec<T, C>(x, y, z);
}
template<typename T, unsigned int C>
auto Vec<T, C>::to_std_vector() const -> std::vector<dataType> {
return std::vector<dataType>(&this->values[0], &this->values[0] + C);
}
template<typename T, unsigned int C>
template<typename TT, unsigned int CC>
Vec<TT, CC> Vec<T, C>::to() const {
Vec<TT, CC> result;
for ( unsigned int i = 0; i < std::min(C, CC); ++i )
result[i] = static_cast<TT>(this->values[i]);
return result;
}
template<typename T, unsigned int C>
auto Vec<T, C>::operator[](int index) -> dataType_ref {
VEC_ASSERT(index < C);
return this->values[index];
}
template<typename T, unsigned int C>
auto Vec<T, C>::operator()(int index) -> dataType_ref {
VEC_ASSERT(index < C);
return this->values[index];
}
template<typename T, unsigned int C>
auto Vec<T, C>::operator[](int index) const -> dataType_cref {
VEC_ASSERT(index < C);
return this->values[index];
}
template<typename T, unsigned int C>
auto Vec<T, C>::operator()(int index) const -> dataType_cref {
VEC_ASSERT(index < C);
return this->values[index];
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator+(T v) {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] + v;
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator-(T v) {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] - v;
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator*(T v) {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] * v;
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator/(T v) {
VEC_ASSERT(v != 0);
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] / v;
return result;
}
template<typename T, unsigned int C>
Vec<T, C>& Vec<T, C>::operator+=(T v) {
for ( unsigned int i = 0; i < C; ++i )
this->values[i] += v;
return *this;
}
template<typename T, unsigned int C>
Vec<T, C>& Vec<T, C>::operator-=(T v) {
for ( unsigned int i = 0; i < C; ++i )
this->values[i] -= v;
return *this;
}
template<typename T, unsigned int C>
Vec<T, C>& Vec<T, C>::operator*=(T v) {
for ( unsigned int i = 0; i < C; ++i )
this->values[i] *= v;
return *this;
}
template<typename T, unsigned int C>
Vec<T, C>& Vec<T, C>::operator/=(T v) {
VEC_ASSERT(v != 0);
for ( unsigned int i = 0; i < C; ++i )
this->values[i] /= v;
return *this;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator+(const Vec<T, C>& v) {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] + v[i];
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator-(const Vec<T, C>& v) {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] - v[i];
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator*(const Vec<T, C>& v) {
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] * v[i];
return result;
}
template<typename T, unsigned int C>
Vec<T, C> Vec<T, C>::operator/(const Vec<T, C>& v) {
for ( unsigned int i = 0; i < C; ++i )
VEC_ASSERT(v[i] != 0);
Vec<T, C> result;
for ( unsigned int i = 0; i < C; ++i )
result[i] = this->values[i] / v[i];
return result;
}
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<unsigned int, 2> Vec2u;
typedef Vec<unsigned int, 3> Vec3u;
typedef Vec<unsigned int, 4> Vec4u;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<char, 2> Vec2c;
typedef Vec<char, 3> Vec3c;
typedef Vec<char, 4> Vec4c;
typedef Vec<unsigned char, 2> Vec2uc;
typedef Vec<unsigned char, 3> Vec3uc;
typedef Vec<unsigned char, 4> Vec4uc;
-
\$\begingroup\$ Welcome to Code Review! I rolled back your edits. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information \$\endgroup\$Sᴀᴍ Onᴇᴌᴀ– Sᴀᴍ Onᴇᴌᴀ ♦2018年08月24日 19:49:21 +00:00Commented Aug 24, 2018 at 19:49
-
\$\begingroup\$ @SamOnela Sure, I will change it back. \$\endgroup\$DimChtz– DimChtz2018年08月24日 19:51:12 +00:00Commented Aug 24, 2018 at 19:51
-
\$\begingroup\$ I already did that. \$\endgroup\$Sᴀᴍ Onᴇᴌᴀ– Sᴀᴍ Onᴇᴌᴀ ♦2018年08月24日 19:51:58 +00:00Commented Aug 24, 2018 at 19:51
-
\$\begingroup\$ @SamOnela Okay. I just thought I should include what rsy56640 suggested. \$\endgroup\$DimChtz– DimChtz2018年08月24日 19:52:06 +00:00Commented Aug 24, 2018 at 19:52
-
\$\begingroup\$ Which C++ version are you using? Can I assume C++17? \$\endgroup\$Rakete1111– Rakete11112018年08月25日 09:29:26 +00:00Commented Aug 25, 2018 at 9:29
2 Answers 2
Finally a real vector class :P.
Any reason why you use
VEC_ASSERT
instead of justassert
. I don't really see the advantage of doing so.using
declarations are nicer thantypedef
s IMO:using dataType = T;
If you use exceptions, mark functions that don't or shouldn't throw
noexcept
.Make use of the injected-class-name:
Vec cross(const Vec &v) const;
You should implement all the
@=
operators for vectors. Also unary-
.Use standard algorithms:
std::fill(values, values + C, 0); // default constructor, second one too std::copy(v.begin(), v.end(), values); // std::vector constructor, init list std::inner_product(v.begin(), v.end(), v.begin(), 0); // length/dot std::transform(values, values + C, result.values, [length](const auto& value) { return values / length; }); // norm, operator@ assert(std::all_of(v.values, v.values + C, [](const auto& value) { return value != 0; })); // operator/
You don't have to use
this->
everywhere you know :).You should consider using
assert
messages:assert(v[0] == 0 && "the first element must be 0!");
No love for
long double
andsigned char
? They don't have aliases.You don't fill the rest of the elements to 0 in your
std::vector
, initializer list constructors and into
.You don't need
push
by changing your signature a bit and doing:template <typename T, unsigned int C> template <typename... Args> Vec<T, C>::Vec(Args&&... args) : Vec{std::forward<Args>(args)...} { static_assert(sizeof...(Args) <= C, "too many arguments to vector"); }
There are techniques to avoid implementing similar code between
operator@
andoperator@=
:friend Foo operator+(Foo lhs, const Foo& rhs) { lhs += rhs; return lhs; } Foo& operator+=(const Foo& rhs) const { // do logic return *this; }
More information very good advice for overloading operator can be found on cppref.
If you want you can reduce the code duplicate of cv qualified
operator[]
s by usingconst_cast
.Please prohibit creating a vector of length 0! :)
You could provide rvalue overloads of
operator[]
to enable efficient moving, but you don't have to. This is overkill, mostly used in the standard library. Have a look atstd::optional::operator*
to see this in action.You might want to consider adding a constructor that takes a pair of iterators, so that
Vec
can be initialized by anything really, not just astd::vector
.Consider making everything
constexpr
.
-
\$\begingroup\$ Thanks a lot for all your suggestions. 1) No, there was something else I wanted to do and I didn't, unfortunately I can't re-post the code. 2) I could use
using
, 3) yes, I will use exceptions, 4) does this work for the implementation as well? 5) if you mean==, !=, <, >
etc, I will. I am just thinking 2 possible ways: a)==
returnstrue
orfalse
if both vectors are the same or not and b)==
returns a vector of bools (like matlab does) 6) yes, I should and I will, 7) I know, it's just my style, 8) I am still thinking if I should go with assert or exceptions or both (any suggestion)? \$\endgroup\$DimChtz– DimChtz2018年08月25日 11:12:26 +00:00Commented Aug 25, 2018 at 11:12 -
\$\begingroup\$ @DimChtz 5) Oh yeah that too. I meant
Vec& operator+=(const Vec&)
\$\endgroup\$Rakete1111– Rakete11112018年08月25日 11:14:18 +00:00Commented Aug 25, 2018 at 11:14 -
\$\begingroup\$ 9) Yes, eventually I will add more aliases, 10) True, I will fix this, 11) Nice, I didn't know this, 12) will do that too, 13) i didn't think of that :P, 14) I was thinking of that, should I use
enable_if
?, 15) I think I lost you here :P, 16) I will \$\endgroup\$DimChtz– DimChtz2018年08月25日 11:15:09 +00:00Commented Aug 25, 2018 at 11:15 -
\$\begingroup\$ @DimChz Use asserts for precondition violation. I'd use asserts in your case, because they should be guaranteed by the caller. \$\endgroup\$Rakete1111– Rakete11112018年08月25日 11:15:56 +00:00Commented Aug 25, 2018 at 11:15
-
\$\begingroup\$ Yes, I will eventually include everything. I am still doing changes \$\endgroup\$DimChtz– DimChtz2018年08月25日 11:16:00 +00:00Commented Aug 25, 2018 at 11:16
template<typename T, unsigned int C>
class Vec;
The template generates redundant codes if C
varies for the same T
.
You can look through <<Effective C++>> Item 44
and refactor like this:
template<typename T>
class VecBase {
T* data;
public:
VecBase(std::size_t C);
};
template<typename T, unsigned int C>
class Vec :VecBase<T> {
public:
Vec() :VecBase<T>(C)
{}
};
The benefit is:
- prevent software from being too large.
- shrink the size of
Vec<T, C>
(data is allocated on the heap, just contains a pointer)
-
\$\begingroup\$ Thanks for your answer. Should I use
VecBase
only for storing the data and keep everything else insideVec
? \$\endgroup\$DimChtz– DimChtz2018年08月24日 14:44:05 +00:00Commented Aug 24, 2018 at 14:44 -
\$\begingroup\$ Right, and remember to correctly implement copy/move/dtor. \$\endgroup\$rsy56640– rsy566402018年08月24日 15:06:53 +00:00Commented Aug 24, 2018 at 15:06
-
1\$\begingroup\$ I think this:
Vec() :VecBase(C)
should be:Vec() : VecBase<T>(C)
. \$\endgroup\$DimChtz– DimChtz2018年08月24日 18:35:46 +00:00Commented Aug 24, 2018 at 18:35 -
1\$\begingroup\$ Please don't use the term heap it is not meaningful in a C++ context (Don't be fooled because Java uses these terms). There is no reference to heap/stack in the C++ standard for a good reason. The terms you should be using are automatic/static/dynamic storage duration objects. Here the difference is between automatic and dynamic objects. \$\endgroup\$Loki Astari– Loki Astari2018年08月28日 06:25:42 +00:00Commented Aug 28, 2018 at 6:25