I am trying to implement functionality similar to boost/operators.
Here is what I have so far:
template<typename T, typename TAG>
struct strong_type{
explicit strong_type(T v) : v(v){}
T v;
};
template<typename T, typename TAG>
struct addition{
addition() = default;
using N = strong_type<T, TAG>;
friend N operator+(const N &a, const N &b){
return N{ a.v + b.v };
}
};
struct myint_tag{};
struct myint :
strong_type<int, myint_tag>,
addition<int, myint_tag>{
using strong_type<int, myint_tag>::strong_type;
myint(const strong_type &other) : strong_type(v){}
};
int main(){
myint a{ 2 };
myint b{ 3 };
// result is not myint, but strong_type<int, myint_tag>
myint c = a + b;
}
However I don't see how this can be implemented without #define.
Is there a way to implement this without need to write myint(const strong_type &other)?
template<class D>
struct strong_add {
friend D operator+( D lhs, D const& rhs ) {
lhs += rhs; return lhs;
}
friend D& operator+=( D& lhs, D const& rhs ) {
lhs.v += rhs.v;
return lhs;
}
};
struct myint :
strong_type<int, myint_tag>,
strong_add<myint> {
using strong_type<int, myint_tag>::strong_type;
};
Live example.
This uses the CRTP. + takes the lhs argument by value, because if you have cheap-to-move expensive-to-copy types like std::string:
a + b + c + d + e
with a naive const&, const& plus, we get a copy every +, as we create a brand new object at each return point from the operator.
With a value, const& plus, first a is copied. Then we do += b, then move the result, then += c then move the result, then += e then move the result. Only one copy is made.
We can go further if you want.
First we do this:
template<class T>
class detect_strong_type {
template<class X, class Tag>
static std::true_type tester( strong_type<X, Tag>const* );
static std::false_type tester( void* );
public:
using type=decltype( tester( (T*)nullptr ) );
};
template<class T>
using is_strong_type = typename detect_strong_type<T>::type;
enum class operators {
add, subtract, multiply, divide
};
template<operators o>
using op_tag_t = std::integral_constant<operators, o>;
template<operators o>
constexpr op_tag_t<o> op_tag{};
auto default_op( op_tag_t<operators::add> ) { return [](auto& lhs, auto const& rhs){ lhs += rhs; }; }
auto default_op( op_tag_t<operators::subtract> ) { return [](auto& lhs, auto const& rhs){ lhs -= rhs; }; }
auto default_op( op_tag_t<operators::multiply> ) { return [](auto& lhs, auto const& rhs){ lhs *= rhs; }; }
auto default_op( op_tag_t<operators::divide> ) { return [](auto& lhs, auto const& rhs){ lhs /= rhs; }; }
template<operators op, class D, class...Skip>
void do_operator( op_tag_t<op>, D& lhs, D const& rhs, Skip&&... ) {
default_op( op_tag<op> )( lhs, rhs );
}
template<class D>
struct can_add {
friend D operator+( D lhs, D const& rhs ) {
lhs += rhs; return lhs;
}
friend D& operator+=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::add>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_subtract {
friend D operator-( D lhs, D const& rhs ) {
lhs -= rhs; return lhs;
}
friend D& operator-=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::subtract>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_multiply {
friend D operator*( D lhs, D const& rhs ) {
lhs *= rhs; return lhs;
}
friend D& operator*=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::multiply>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_divide {
friend D operator/( D lhs, D const& rhs ) {
lhs *= rhs; return lhs;
}
friend D& operator/=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::divide>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_math:
can_add<D>, can_multiply<D>, can_subtract<D>, can_divide<D>
{};
now we teach do_operator about strong_type:
template<operators op, class D,
std::enable_if_t< is_strong_type<D>{}, bool> =true
>
void do_operator( op_tag_t<op>, D& lhs, D const& rhs ) {
do_operator( op_tag<op>, lhs.v, rhs.v );
}
and this works:
struct myint :
strong_type<int, myint_tag>,
can_math<myint>
{
using strong_type<int, myint_tag>::strong_type;
};
int main(){
myint a{ 2 };
myint b{ 3 };
myint c = a*b + b - a;
}
Live example
Now this is a bit overkill just for strong operators. What it does let you do is:
struct some_type: can_add<some_type> {
std::vector<int> values;
friend void do_operator( op_tag_t<operators::add>, some_type& lhs, some_type const& rhs ) {
lhs.values.insert( lhs.values.end(), rhs.values.begin(), rhs.values.end() );
}
};
and now some_type + some_type and some_type += some_type are implemented.
Yakk - Adam Nevraumont is definitely very good,
but I found much easier and clear way to do the same.
If you think a bit, all these addition / strong_add classes, need to inject "global" operators.
The operators itself do not need to be friend, but friend keyword is used, because else the operator will be not injected into "global" space.
Also no one needs the struct. It is used only to inject the operators, so it might be empty struct.
template<typename T, typename TAG>
struct strong_type{
using type = T;
T v;
explicit constexpr strong_type(const T &v) : v(v) {}
};
Then surprisingly:
template<class V>
struct arithmetic{
friend constexpr V operator+ (const V &a, const V &b){ return { a.v + b.v }; }
friend constexpr V operator- (const V &a, const V &b){ return { a.v - b.v }; }
friend constexpr V operator* (const V &a, const V &b){ return { a.v * b.v }; }
friend constexpr V operator/ (const V &a, const V &b){ return { a.v / b.v }; }
friend V &operator+=(V &a, const V &b){ a.v += b.v; return a; }
friend V &operator-=(V &a, const V &b){ a.v -= b.v; return a; }
friend V &operator*=(V &a, const V &b){ a.v *= b.v; return a; }
friend V &operator/=(V &a, const V &b){ a.v /= b.v; return a; }
};
and finally:
struct myint_tag{};
struct myint : strong_type<int, myint_tag>,
arithmetic <myint>
{
using strong_type::strong_type;
};
int main(){
constexpr myint a{ 2 };
constexpr myint b{ 3 };
myint x{ 3 };
myint y{ 5 };
x = x + y * x;
}
Related
I tried writing a class with operators two different ways. First, I tried it with the operators defined inside the class. Then, I tried it with the operators defined outside the class. Defining the operators outside the class appears to be better because I can take advantage of implicit conversions on the left-hand-operand. It appears to be widely recommended that operators be defined outside the class when possible.
However, when I make the class a template class, implicit conversions on the left-hand-operand no longer work. In fact, implicit conversions on the right-hand-operand also do not work. Am I doing something wrong?
namespace N1 {
template <class T>
class C1 {
public:
double value;
/* implicit */ C1(double value) : value{value} {}
C1(const C1<T>& other) : value{other.value} {}
C1<T>& operator=(const C1<T>& other) {
this->value = other.value;
return *this;
}
// Define operator+ for C1 inline
inline C1<T> operator+(const C1<T>& other) const {
return this->value + other.value;
}
};
template <class T>
class C2 {
public:
double value;
/* implicit */ C2(double value) : value{value} {}
C2(const C2<T>& other) : value{other.value} {}
C2<T>& operator=(const C2<T>& other) {
this->value = other.value;
return *this;
}
};
// Define operator+ for C2 out-of-line
template <class T>
inline C2<T> operator+(const C2<T>& self, const C2<T>& other) {
return self.value + other.value;
}
} // namespace N1
namespace {
using C1 = N1::C1<int>;
using C2 = N1::C2<int>;
// double f1(double x, const C1& y) {
// return (x + y).value; // not expected to work
// }
double f2(const C1& x, double y) {
return (x + y).value; // works
}
double f3(double x, const C2& y) {
// Works when C2 is a class, fails when C2 is a template class
return (x + y).value;
}
double f4(const C2& x, double y) {
// Works when C2 is a class, fails when C2 is a template class
return (x + y).value;
}
} // namespace
my hope is that clients should be able to write code such as
void my_main() {
N1::C2<Anything> x{4};
auto y = x + 2.0;
auto z = 2.0 + x;
}
You can fix C1's first test case by adding the out of line operator+
template<class T>
C1<T> operator+(double d, const C1<T>& c1) {
return c1 + d;
}
to get both test cases to pass:
double f1(double x, const C1& y) {
return (x + y).value; // not expected to work - but now works
}
double f2(const C1& x, double y) {
return (x + y).value; // works
}
The C2 tests: To fix those, you need to make N1::C2<int> a dependent type. You can do that with a bit of SFINAE which considers all N1::C2<T>s, but only accepts it when T is int. Note that it's a problem with the test cases - not with implicit conversion.
template<class T>
std::enable_if_t<std::is_same_v<T,int>, double>
f3(double x, const N1::C2<T>& y) {
return (x + y).value; // implicit conversion works
}
template<class T>
std::enable_if_t<std::is_same_v<T,int>, double>
f4(const N1::C2<T>& x, double y) {
return (x + y).value; // implicit conversion works
}
or using your C2 typedef which makes it consider all T's but only accepts N1::C2<int>:
using C2 = N1::C2<int>;
template<class T>
std::enable_if_t<std::is_same_v<T,C2>, double>
f3(double x, const T& y) {
return (x + y).value; // implicit conversion works
}
template<class T>
std::enable_if_t<std::is_same_v<T,C2>, double>
f4(const T& x, double y) {
return (x + y).value; // implicit conversion works
}
In the comments you say you want the functions to accept all N1::C2<T>s and then it becomes simpler:
template<class T>
auto f3(double x, const N1::C2<T>& y) {
return (x + y).value;
}
template<class T>
auto f4(const N1::C2<T>& x, double y) {
return (x + y).value;
}
My hope is that clients can write code such as ... Is that not possible?
N1::C2<int> x{4};
auto y = x + 2.0;
auto z = 2.0 + x;
Yes, but you then need to add overloads for that:
template <class T>
C2<T> operator+(const C2<T>& lhs, double rhs) {
return lhs.value + rhs;
}
template <class T>
C2<T> operator+(double lhs, const C2<T>& rhs) {
return rhs + lhs; // just swapped the order to use the above
}
Another option, which is how it's commonly done, is to add the operator+= member function:
template<class T>
class C2 {
// ...
C2& operator+=(const C2& other) {
value += other.value;
return *this;
}
};
You could then define the free functions like so:
template <class T>
C2<T> operator+(const C2<T>& lhs, std::convertible_to<C2<T>> auto&& rhs) {
auto rv = lhs;
rv += rhs;
return rv;
}
template <class T, class U>
std::enable_if_t<!std::same_as<std::decay_t<U>*, C2<T>*>, C2<T>>
operator+(const U& lhs, const C2<T>& rhs) {
return rhs + lhs;
}
Demo
Instead of SFINAE as above, you could create a home made concept to avoid ambiguity when adding two C2<T>s:
template <class From, class To>
concept convertible_to_but_is_not =
not std::same_as<std::remove_reference_t<From>, To> &&
std::convertible_to<From, To>;
template <class T>
C2<T> operator+(const C2<T>& lhs, std::convertible_to<C2<T>> auto&& rhs) {
auto rv = lhs;
rv += rhs;
return rv;
}
template <class T> // making sure that lhs is not a C2<T>
C2<T> operator+(convertible_to_but_is_not<C2<T>> auto&& lhs, const C2<T>& rhs) {
return rhs + lhs;
}
Demo
Per Ted's answer:
Implicit conversions are not considered from function arguments during template argument deduction. This problem can be addressed by the addition of two helper functions:
template <class T>
inline C2<T> operator+(double self, const C2<T>& other) {
return self + other.value;
}
template <class T>
inline C2<T> operator+(const C2<T>& self, double other) {
return self.value + other;
}
Consider the following set of classes and the relationship of their operators: We can implement them in two distinct ways. The first where the operators are defined within the class, and the latter where they are defined outside of the class...
template<typename T>
struct A {
T value;
T& operator+(const A<T>& other) { return value + other.value; }
// other operators
};
temlate<typename T>
struct B {
T value;
T& operator+(const B<T>& other) { return value + other.value; }
};
// Or...
template<typename T>
struct A {
T value;
};
template<typename T>
T& operator+(const A<T>& lhs, const A<T>& rhs) { return lhs.value + rhs.value; }
// ... other operators
template<typename T>
struct B {
T value;
};
template<typename T>
T& operator+(const B<T>& lhs, const B<T>& rhs) { return lhs.value + rhs.value; }
// ... other operators
Is there any way in C++ where I would be able to make a single class or struct of operators to where I could simply be able to declare or define them within any arbitrary class C without having to write those same operators multiple times for each class? I'm assuming that the operators will have the same behavior and property for each distinct class that defines them considering that they will all follow the same pattern.
For example:
template<typename T, class Obj>
struct my_operators {
// define them here
};
// Then
template<typename T>
struct A {
T value;
my_operators ops;
};
template<typename T>
struct B {
T value;
my_operators ops;
};
Remember I'm restricting this to C++17 as I'm not able to use any C++20 features such as Concepts... If this is possible, what kind of method or construct would I be able to use, what would its structure and proper syntax look like? If this is possible then I'd be able to write the operators once and just reuse them as long as the pattern of the using classes matches without having to write those operators for each and every individual class...
What about using CRTP inheritance?
#include <iostream>
template <typename T>
struct base_op
{
auto operator+ (T const & o) const
{ return static_cast<T&>(*this).value + o.value; }
};
template<typename T>
struct A : public base_op<A<T>>
{ T value; };
template<typename T>
struct B : public base_op<B<T>>
{ T value; };
int main()
{
A<int> a1, a2;
B<long> b1, b2;
a1.value = 1;
a2.value = 2;
std::cout << a1+a2 << std::endl;
b1.value = 3l;
b2.value = 5l;
std::cout << b1+b2 << std::endl;
}
Obviously this works only for template classes with a value member.
For the "outside the class" version, base_op become
template <typename T>
struct base_op
{
friend auto operator+ (T const & t1, T const & t2)
{ return t1.value + t2.value; }
};
-- EDIT --
The OP asks
now I'm struggling to write their equivalent +=, -=, *=, /= operators within this same context... Any suggestions?
It's a little more complicated because they must return a reference to the derived object... I suppose that (for example) operator+=(), inside base_op, could be something as
T & operator+= (T const & o)
{
static_cast<T&>(*this).value += o.value;
return static_cast<T&>(*this);
}
Taking the answer provided by user max66 using CRTP and borrowing the concept of transparent comparators provided by the user SamVarshavchik within the comment section of my answer, I was able to adopt them and came up with this implementation design:
template<class T>
struct single_member_ops {
friend auto operator+(T const & lhs, T const & rhs)
{ return lhs.value + rhs.value; }
friend auto operator-(T const & lhs, T const & rhs)
{ return lhs.value - rhs.value; }
template<typename U>
friend auto operator+(T const& lhs, const U& rhs)
{ return lhs.value + rhs.value; }
template<typename U>
friend auto operator-(T const& lhs, const U& rhs )
{ return lhs.value - rhs.value;}
};
template<typename T>
struct A : public single_member_ops<A<T>>{
T value;
A() = default;
explicit A(T in) : value{in} {}
explicit A(A<T>& in) : value{in.value} {}
auto& operator=(const T& rhs) { return value = rhs; }
};
template<typename T>
struct B : public single_member_ops<B<T>> {
T value;
B() = default;
explicit B(T in) : value{in} {}
explicit B(B<T>& in) : value{in.value} {}
auto& operator=(const T& rhs) { return value = rhs; }
};
int main() {
A<int> a1(4);
A<int> a2;
A<int> a3{0};
a2 = 6;
a3 = a1 + a2;
B<double> b1(3.4);
B<double> b2(4.5);
auto x = a1 + b2;
auto y1 = a2 - b2;
auto y2 = b2 - a1;
return x;
}
You can see that this will compile found within this example on Compiler Explorer.
The additional templated operator allows for different types: A<T> and B<U> to use the operators even if T and U are different for both A and B provided there is a default conversion between T and U. However, the user will have to be aware of truncation, overflow & underflow, and narrowing conversions depending on their choice of T and U.
I have a given utility template class tagged. I had to declare 2 new structs using these template classes as follows.
tagged.h
#ifndef TAGGED_H
#define TAGGED_H
#include <iostream>
template<typename T, typename TAG>
class tagged
{
private:
T _value;
public:
tagged() : _value() { }
explicit tagged(const T& value) : _value(value) { }
// https://isocpp.org/wiki/faq/templates#template-friends
friend T& value(tagged<T, TAG>& st)
{
return st._value;
}
friend const T& value(const tagged<T, TAG>& st)
{
return st._value;
}
};
template<typename T>
struct equality
{
friend bool operator ==(const T& x, const T& y)
{
return value(x) == value(y);
}
friend bool operator !=(const T& x, const T& y)
{
return value(x) != value(y);
}
};
template<typename T>
struct ordered : equality<T>
{
friend bool operator <(const T& x, const T& y)
{
return value(x) < value(y);
}
friend bool operator <=(const T& x, const T& y)
{
return value(x) <= value(y);
}
friend bool operator >(const T& x, const T& y)
{
return value(x) > value(y);
}
friend bool operator >=(const T& x, const T& y)
{
return value(x) >= value(y);
}
};
These are the two structs i declared following the rules given by the assignment.
primitives.h
//Time
struct __declspec(empty_bases)Time : tagged<uint64_t, Time>, ordered<Time>, show_value<Time, int>{ using tagged::tagged; };
//Duration
struct __declspec(empty_bases)Duration : tagged<uint64_t, Duration>, ordered<Duration>, show_value<Duration, int> { using tagged::tagged; };
I succeeded in writing all other operators like + and - but i cant seem to solve how to overload += and -= I'm not allowed to change the objects in tagged.h I know assignment operators can only be member functions. Because of the way the template works i've tried casting 'const Time&' and const Duration& to non consts but that didnt seem to work. I've tried the examples you can find online about assigment operator overloading but the examples all overload in the template and not in the inherited class where I barely have write access to '_value' which is the value I should overwrite of reassign the pointer of.
Thanks
edit:
struct __declspec(empty_bases)Time : tagged<uint64_t, Time>, ordered<Time>, show_value<Time, int>
{
using tagged::tagged;
Time& operator+(const Duration& right) {
Time t = Time(value(*this) + value(right));
return t;
};
Time& operator+=(const Duration& right) {
(uint64_t&)(*this) = value(*this) + value(right);
return (*this);
};
};
//Duration
struct __declspec(empty_bases)Duration : tagged<uint64_t, Duration>, ordered<Duration>, show_value<Duration, int> {
using tagged::tagged;
Duration& operator+(const Duration& right) {
Duration d = Duration(value(*this) + value(right));
return d;
};
Time& operator+(const Time & right) {
Time t = Time(value(*this) + value(right));
return t;
};
Duration& operator-(const Time & right) {
Duration d = Duration(value(*this) - value(right));
return d;
};
Duration& operator-(const Duration & right) {
Duration d = Duration(value(*this) - value(right));
return d;
};
Duration& operator+=(const Duration& right) {
(uint64_t&)(*this) = (uint64_t&)(*this) + (uint64_t&)(right);
return (*this);
}
Duration& operator-=(const Duration& right) {
(uint64_t&)(*this) = value(*this) - value(right);
return (*this);
};
};
This is what I have now. Still have the same syntax errors that keep popping up. I dont know anymore lmao
From what I see you should be able to implement it using value() function; Since value returns by reference something like this should work:
Duration & operator+=(const Duration & right) {
value(*this) += value( right );
return *this;
}
Just be careful on the other operators (I'm looking at + and -) because you return references to temporal objects.
Duration & operator+(const Duration & right) { // this returns a reference to an object
Duration d = Duration( value( *this ) + value( right ) ); // this creates a temporal variable
return d; // you return a reference to d bu its lifetime is over -> undefined behavior I believe
}
I want to initialize a container with pointers to objects. I currently have a loop like this:
for(int i=0;i < n;i++) {
container.push_back(new Object());
}
Which C++ operation (i.e. similar to std::transform) is the right to replace this loop and initialize a container with n newly created objects?
Use std::generate:
constexpr int n = 10;
std::vector<Object*> v1(n);
std::generate(v1.begin(), v1.end(), [](){ return new Object(); });
or std::generate_n:
std::vector<Object*> v2;
v2.reserve(n); // pre-allocate sufficient memory to prevent re-allocations
// (you should have done in original loop approach as well)
std::generate_n(std::back_inserter(v2), n, [] { return new Object(); });
You could use std::generate_n and std::back_inserter with lambda.
std::generate_n(std::back_inserter(container), n, [] { return new Object(); });
The goal is this syntax:
std::vector<Object*> v1 = generate([](auto&&){ return new Object; }, 10).make_container();
where we say we want to generate 10 elements with a specific lambda, then we create a container of the asked for type.
It requires some boilerplate. First, an input iterator that counts and calls a function:
template<class F>
struct generator_iterator {
F f;
std::size_t i = 0;
using self=generator_iterator;
friend bool operator==(self const& lhs, self const& rhs){ return lhs.i==rhs.i; }
friend bool operator!=(self const& lhs, self const& rhs){ return lhs.i!=rhs.i; }
using reference=std::result_of_t<F const&(std::size_t const&)>;
using value_type=std::decay_t<reference>;
using difference_type=std::ptrdiff_t;
using pointer=value_type*;
using iterator_category=std::input_iterator_tag;
self& operator++(){++i; return *this;}
self operator++(int){auto tmp=*this; ++*this; return tmp;}
reference operator*()const{ return f(i); }
pointer operator->()const { return std::addressof(f(i)); }
friend difference_type operator-( self const& lhs, self const& rhs ) { return lhs.i-rhs.i; }
self& operator-=( difference_type rhs )& {
i-=rhs;
return *this;
}
self& operator+=( difference_type rhs )& {
i+=rhs;
return *this;
}
friend difference_type operator+( self lhs, difference_type rhs ) {
lhs += rhs;
return lhs;
}
friend difference_type operator-( self lhs, difference_type rhs ) {
lhs -= rhs;
return lhs;
}
};
Next, a range primitive, with a .make_container() method that lets you convert ranges to containers either by passing the type explicitly or implicitly:
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
private:
struct container_maker {
range_t const* self;
template<class C>
operator C()&& {
return {self->begin(), self->end()};
}
};
public:
container_maker make_container()const{
return {this};
}
// C is optional
template<class C>
C make_container()const{
return make_container();
}
};
template<class It>
range_t<It> range( It s, It f ) {
return {std::move(s), std::move(f)};
}
We then glue these together:
template<class F>
auto generate( F&& f, std::size_t count ) {
generator_iterator<std::decay_t<F>> e{f, count};
generator_iterator<std::decay_t<F>> b{std::forward<F>(f)};
return range( std::move(b), std::move(e) );
}
and this compiles:
std::vector<Object*> v1 = generate([](auto&&){ return new Object; }, 10).make_container();
Live 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