mk12
mk12

Reputation: 26374

C++ arithmetic operator overloading—automatic widening?

I have a Vector class which represents a 2D vector. It is templated to allow any numerical type to be used for the x and y components. As an example, one of the arithmetic operators I overload is * for multiplying a vector with a scalar:

template <typename T, typename U>
inline const Vector<T> operator*(const Vector<T>& vector, U scalar) {
    return Vector<T>(vector.x * scalar, vector.y * scalar);
}

(I also have a function with the parameters in the opposite order to allow scalar * Vector in addition to Vector * scalar).

As you can see, I use <T, U> instead of simply <T> so that the scalar doesn't have to be the same type as the Vector. When I didn't do this, surprisingly Vector<double> * int wouldn't compile (I thought the int would automatically widen).

In any case, I don't simply want to return a Vector<T>. I want to mimic the built-in types and return whichever has higher precision, T or U. So for example, Vector<int> * double => Vector<double> while Vector<double> * short => Vector<double>.

Is this possible?

Upvotes: 4

Views: 1374

Answers (2)

There are two solutions to this. In Pre C++11 you can write a template like:

template <typename T, typename U>
struct WhatWillItBe {
  typedef T result_t;
};

template <typename T>
struct WhatWillItBe<T, double> {
  typedef double result_t;
};

// ... lots more

etc. and write a lot of specialisations, then you can use that to look up the return type, e.g.:

template <typename T, typename U>
inline const Vector<typename WhatWillItBe<T,U>::result_t> operator*(const Vector<T>& vector, U scalar) {
    return Vector<typename WhatWillItBe<T,U>::result_t>(vector.x * scalar, vector.y * scalar);
}

Alternatively C++11 makes this straightforward, you can write auto for the return type and use -> to specify the return type after the rest of the function:

template <typename T, typename U>
inline auto operator*(const Vector<T>& vector, U scalar) -> Vector<decltype(vector.x*scalar)> {
    return Vector<decltype(vector.x*scalar)>(vector.x * scalar, vector.y * scalar);
}

Which allows you to use decltype for the return type of a function, setting it based on what would happen for promotion naturally with vector.x * scalar.

Upvotes: 4

Kerrek SB
Kerrek SB

Reputation: 477040

You can use common_type or decltype to cook up something that gives you the resulting type; and then you have to create the actual vector:

template <typename A, typename B>
std::vector<typename std::common_type<A, B>::type>
operator*(std::vector<A> const & v, B const & x)
{
    std::vector<typename std::common_type<A, B>::type> res;
    res.reserve(v.size());
    for (A a : v) res.push_back(a * x);
    return res;
}

Using decltype, you can get at the result type via:

decltype(std::declval<A>() * std::declval<B>())

For both std::common_type and std::declval you need to #include <type_traits>.

With delayed return types (auto and ->) you can use decltype directly on the function arguments, but using std::declval feels a bit more hygienic, since it doesn't require you to furnish an actual instance of your type (and thus it is applicable even in situations where this isn't possible).

Upvotes: 6

Related Questions