Consider we have a simple mathematical vector class with overloaded operator*
.
We overload operator*
for scalar multiplication and for inner product as well. It can look like this:
#include <iostream>
template<typename T> struct Vec
{
template<typename SCALAR_T>
friend auto operator*(const Vec<T> &v, const SCALAR_T &scalar)
{
return Vec<decltype(T() * SCALAR_T())>{v.x * scalar, v.y * scalar};
}
template<typename SCALAR_T>
friend auto operator*(const SCALAR_T &scalar, const Vec<T> &v)
{
return v * scalar;
}
template<typename S>
friend auto operator*(const Vec<T> &v, const Vec<S> &w)
{
return v.x * w.x + v.y * w.y;
}
T x, y;
};
void templateTest()
{
Vec<int> vi{ 1, 2 };
Vec<float> vf{ 2, 3 };
auto vres = vi * vf;
std::cout << vres;
}
Just as I expected in this case, VS2015 says error C2593: 'operator *' is ambiguous
. Compiler cannot choose between function overloads.
The question is: how do I resolve the ambiguity? Ideally I want the 3rd version to be instantiated only for two vector types (Vec<A>
and Vec<B>
), and for everything else I want scalar version to be used.
Is there a way to use type traits to tell if type is an instantiation of Vec
(or not)?
The common solution I've seen in mathematical libraries is to not overload operator* for inner product, but rather use v.dot(w)
. This is a solution, but for the sake of theoretical interest, I would love to know how to do this with template metaprogramming.
EDIT:
Okay, the one solution could be to use SFINAE to enable first two overloads only for arithmetic types:
template<typename SCALAR_T, typename = std::enable_if_t<std::is_arithmetic<SCALAR_T>::value>>
friend auto operator*(const Vec<T> &v, const SCALAR_T &scalar)
{
return Vec<decltype(T() * SCALAR_T())>{v.x * scalar, v.y * scalar};
}
template<typename SCALAR_T, typename = std::enable_if_t<std::is_arithmetic<SCALAR_T>::value>>
friend auto operator*(const SCALAR_T &scalar, const Vec<T> &v)
{
return v * scalar;
}
But this is not an ideal solution, it allows only integral and floating point types as SCALAR_T, but I'd rather want it accept anything other than Vec
. So, the question is, how to achieve the following (pseudocode):
template<typename SCALAR_T, typename = std::enable_if_t<not_instantiation<SCALAR_T, Vec>::value>>