0

i need to dynamically create arrays containing different numerical types, including char, int, unsigned int, float, double. i'd like to be able to create any two of these arrays and, assuming they're of the same length, implement arithmetic operators such as operator+=

i've been able to implement the array as a template, but i don't know how to implement any arithmetic operators, since i can't know what the other array's type will be at compile time, or even when the first array is created (i will know the type for the array i'm constructing). i looked at std::array, but it doesn't support arithmetic operators. another alternative, which is definitely not elegant (but does work), is to implement a series of type specific operators, such as

MyArray<V> operator+ (const MyArray<float>& addend) const;
MyArray<V> operator+ (const MyArray<double>& addend) const;
MyArray<V> operator+ (const MyArray<int32>& addend) const;
MyArray<V> operator+ (const MyArray<int16>& addend) const;

thanks for any advice.

  • 1
    what do these operators need to do? if they are simple arithmetic then why do you have to write them yourself? – ZivS Apr 18 '14 at 13:52
  • 1
    This is probably an instance where operator overloading is NOT a good choice, IMHO. `std::transform(this->begin(), this->end(), addend.end(), output->begin(), std::plus());` – IdeaHat Apr 18 '14 at 13:54
  • To expand, binary operator overloading only makes sense when you can guarantee that they'll make sense. This seems simple on the surface, but you need to guarantee things like `a+b==b+a`, which your case clearly doesn't (the return types won't agree). In addition, it looks like you are allocating a new array with each operator, which will create unnecessary overhead. – IdeaHat Apr 18 '14 at 13:58
  • @MadScienceDreams that is not always true. e.g. with a matrix class the binary operator * would give a different answer depending on order of input. – RamblingMad Apr 18 '14 at 18:05
  • @CoffeeandCode Yes, I agree I wasn't clear enough. Another example in which the order of operations matters for a concatenate type "plus". The big issue is that, whatever is done, it _must_ make sense, which is not trivial. For a mathmatic addition operator, `a+b` SHOULD equal `b+a`. – IdeaHat Apr 18 '14 at 18:09
  • Yeah, I don't know of a situation where addition is dependant upon order. – RamblingMad Apr 18 '14 at 18:12

3 Answers3

1

Alright, its probably obvious enough from my comments of everything in this thread that this is a particular sore spot for me. And this is for good reason, I was once like you. I was like, I can overload operators! AMAZING! OVERLOAD ALL THE OPERATORS (this was with a custom image container type). After a while, a few things became clear:

  1. Operators are hard to correctly declare, especially templated ones.
  2. Templated operators cannot have their types set explicitely, only implicitly.
  3. Operation order doesn't make sense all the time.
  4. Operators must either use exceptions as their "fail" mode which is not ideal in all cases, or use "enable-if" type syntax if the fail can be detected at compile time.
  5. Operator MEANING is hard to document/elucidate. Different interpretations of what an operator should "do" makes it hard to figure out. (Should the MyArray<T>+MyArray<J> work as a memberwise plus like T+J or should it work like a concatenate like 'string+string'?)
  6. Operators must return by value, which can cause overhead if your moves aren't all set up correctly/you aren't in C++11/for any reason return value elision doesn't happen.
  7. Overall, writing your own container types is a great way to redo alot of work the STL has already done.

You COULD do it like (at a namespace scope) (assuming you have a templated cast operator available)

template <typename T, typename J>
MyArray<decltype(T()+J())> operator+(const MyArray<T>& x,const MyArray<J>& y)
{
   using K=decltype(T()+J());
   MyArray<K> ret(x.size());//or something?
   for (size_t i = 0; i < x.size(); i++) {ret[i]=x[i]+y[i];}//could replace with foreach
   return ret;
};

Though using the following with vectors just makes more sense. You can wrap it in a "add" call if you want.

std::vector<T> a;//or whatever
std::vector<J> b;//or whatever
std::vector<K> c(a.size());//note: you can still use the decl type here, OR just define it to whatever you actually want it to be
std::transform(a.begin(), a.end(). b.begin(), c.begin(), std::plus<K>());

If you are trying to do this all over the place, and are trying to make a matrix math library, use one like Eigen, it'll save you a lot of work, it'll be strongly typed as a matrix and not a generic collection, and it'll be done with the full math knowledge the Eigen team has.

IdeaHat
  • 7,641
  • 1
  • 22
  • 53
0

You can use one more template parameter:

template<class V, class T> MyArray<V> operator+ (const MyArray<T>& addend) const;

Then the cast will always be according to your main array type.

Batuhan Tasdoven
  • 798
  • 7
  • 19
  • No, it is cast to the arbitrary first argument. Lets say A is a `MyArray`, and B is a `MyArray`. A+B will return an `MyArray` (the underlying math will be calculated as `float` and then cast to `int`). `B+A` will return a `MyArray`. `A[0]+B[0]` will return a `float`, `B[0]+A[0]` will return a `float`. You should probably use the promotion rules of C++ or not use operators whose template arguments cannot be explicitly set. – IdeaHat Apr 18 '14 at 14:06
0

You will likely have to dispatch your operations by a result type selected by some type traits.

Simplified for a number (no vector):

#include <iostream>

template <typename T>
struct Number {
    T value;
    Number(T value) : value(value) {}

    template <typename U>
    explicit Number(U value) : value(value) {}

    operator T () const { return value; }
};

#define C_PLUS_PLUS_11 (201103L <= __cplusplus)

template <typename U, typename V>
struct binary_operation_traits {
    #if C_PLUS_PLUS_11
    typedef decltype(U() + V()) result_type;
    #endif
};

#if ! C_PLUS_PLUS_11
template <typename T>
struct binary_operation_traits<T, T> {
    typedef T result_type;
};

template <>
struct binary_operation_traits<int, float> {
    typedef float result_type;
};

template <>
struct binary_operation_traits<int, double> {
    typedef double result_type;
};

// And so on ...

#endif

template <typename U, typename V>
Number<typename binary_operation_traits<U, V>::result_type>
operator + (const Number<U>& a, const Number<V>& b) {
    typedef typename binary_operation_traits<U, V>::result_type result_type;
    return Number<result_type>(result_type(a) + result_type(b));
}

int main ()
{
    Number<int> a(1);
    Number<double> b(1.5);
    std::cout << a + b << '\n';
    return 0;
}