Philip Zhang
Philip Zhang

Reputation: 627

Implicit constructor isn't called for user defined operator with template

In my code, I implement a template of Vector3 like below:

template <typename T>
struct TVector3{
    TVector3(const T& x, const T& y, const T& z);   // normal constructor from x,y,z
    TVector3(const T& val);   // construct from a constant value
    // .. other implementation
    T x, y, z;
};
// and my overload operator+ for TVector3
template <typename T>
const TVector3<T> operator+(const TVector3<T>& lhs, const TVector3<T>& rhs)
{
    return TVector3<T>(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
}

I'm expecting call like this: (1, 2, 3) + 1 = (2, 3, 4), so I wrote it like this:

TVector3<float> v(1, 2, 3);
v + 1.0f;   // error, on operator "+" matches ... operand types are: TVector3<float> + float

Then I seem to understand that template deduction should completely match argument types instead of converting them, so is there any way for me to tell compiler to convert T to TVector3? I don't want to write 2 overloads like below because code would become large:

template <typename T>
const TVector3<T> operator+(const TVector3<T>& lhs, const T& rhs);
template <typename T>
const TVector3<T> operator+(const T& rhs, const TVector3<T>& rhs);

Thanks for helping!

Upvotes: 1

Views: 67

Answers (1)

kec
kec

Reputation: 2149

The trick here is to instantiate a function that is not actually a member when the class template gets instantiated. This can be accomplished via a friend definition (inline friend). When the class template is instantiated, the friend is also instantiated, and also injected into the surrounding namespace (in a restricted way) as a non-templated function, so that conversions will be applied. (I also made the return non-const, since I don't think you intended that.) For further details, lookup "friend name injection", "argument dependent lookup", and the related Barton-Nackman trick.

template <typename T>
struct TVector3{
    friend TVector3<T> operator+(const TVector3<T>& lhs, const TVector3<T>& rhs) {
        return TVector3<T>(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
    }
    TVector3(const T& x, const T& y, const T& z);   // normal constructor from x,y,z
    TVector3(const T& val);   // construct from a constant value
    // .. other implementation
    T x, y, z;
};

int
main() {

    TVector3<float> v(1, 2, 3);
    v + 1.0f; // Works.
}

An important and related thing to note is that there is a difference between a template function and a non-template function, even if the signature is otherwise the same.

template <typename T> class A;

void f(A<int>); // 1

template <typename T>
void f(A<T>); // 2

template <typename T>
class A {
  friend void f(A<T>); // Makes 1 a friend if T == int, but not 2.
  friend void f<>(A<T>); /// Makes 2 also a friend.
};

Upvotes: 5

Related Questions