4

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>>
Aleksei Petrenko
  • 6,698
  • 10
  • 53
  • 87
  • That plus [`std::is_arithmetic`](http://en.cppreference.com/w/cpp/types/is_arithmetic) instead of `is_integral`. – Baum mit Augen Jun 18 '16 at 12:01
  • 2
    Also, I cannot recommend using `operator*` for a dot product, that's not a canonical notation. – Baum mit Augen Jun 18 '16 at 12:02
  • @BaummitAugen see my comment at the end of the question) Yes, I know, in real code, I'd rather use .dot or .dotProduct. – Aleksei Petrenko Jun 18 '16 at 12:04
  • Just use the same SFINAE trick to disable the first two overloads for `Vec`. (Look at [this](https://stackoverflow.com/questions/11251376/how-can-i-check-if-a-type-is-an-instantiation-of-a-given-class-template) for example.) If you run into problems, post a new question with a specific problem statement of what's giving you trouble. – Baum mit Augen Jun 18 '16 at 12:37

0 Answers0