Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated operator instantiation and type conversion

This if for the C++ gurus out there.

Consider the following code:

class X { };

template <class T>
class Mistake {
public:
  T x;

  Mistake(const T& k) : x(k) { }
  Mistake(const X&) : x(1) { }

  void print() { cout << x << endl; }
};

template <class T>
Mistake<T> operator+(const Mistake<T>& a, const Mistake<T>& b)
{
  return Mistake<T>(a.x + b.x);
}

I have a class "Mistake" for which I want to have the addition operation. When I try:

X a, b;
Mistake<int> foo = a + b;

I get a compilation error; the compiler cannot seem to realize that the template operator+ has to be instantiated.

If, on the other hand, I add the following piece of code before:

Mistake<int> operator+(const Mistake<int>& a, const Mistake<int>& b)
{
  return Mistake<int>(a.x + b.x);
}

then all is well. Anyone has any clue why? I suspect the compiler cannot figure out what to instantiate because of the type conversion needed from class X to class Mistake, but I don't know how to fix this issue short of not using templates at all.

By the way, defining the operator inside the class as a friend also doesn't work.

Thanks!

like image 408
Fernando Avatar asked May 10 '13 18:05

Fernando


3 Answers

While others have proposed possible solutions to your problem, I would like to point out what is going on, and why your expectation cannot be met.

The problem here is that user-defined conversions are not considered when performing type deduction. When the compiler is presented with this expression:

a + b

Where both a and b are of type X, and the following signature for operator +:

template <class T>
Mistake<T> operator+(const Mistake<T>& a, const Mistake<T>& b)

The first thing the compiler will do is to try and deduce T so that the types of the parameters of the operator match exaclty the types of the arguments. If this is not possible, the compiler immediately gives up, without considering possible converting constructors, and focuses on other candidate functions (or function templates).

Considering the situation above, it is clear that there is no way to make Mistake<T> become exaclty X, whatever T you pick (Mistake<int> is not X, Mistake<X> is not X, and so on). Therefore, substitution fails and the compiler does not know how to resolve the call because there are no other candidates around.

On the other hand, when you have this:

Mistake<int> operator+(const Mistake<int>& a, const Mistake<int>& b)

There is no type deduction involved, because the above is not a function template. Therefore, the compiler will take user-defined conversions into account when trying to resolve the call, and since Mistake<int> has a constructor that accepts an X, the above operator + is considered a viable candidate, and it gets picked.

like image 157
Andy Prowl Avatar answered Nov 15 '22 16:11

Andy Prowl


I don't think there is a way. The purest you can achieve is

Mistake<int> foo = static_cast<Mistake<int>>(a) + static_cast<Mistake<int>>(b);

Or if you push it a little using an additional overload that matches assymetric operand types:

template <class T, class U>
Mistake<T> operator+(const Mistake<T>& a, U const& b) {
    return a + static_cast<Mistake<T>>(b);
}

    // and later:
    foo = Mistake<int>(a) + b;

Full live demo: http://ideone.com/ki14GO

#include <iostream>

class X { };

template <class T>
class Mistake {
    public:
        T x;

        Mistake(const T& k) : x(k) { }
        Mistake(const X&) : x(1) { }

        void print() { std::cout << x << std::endl; }
};

template <class T>
Mistake<T> operator+(const Mistake<T>& a, const Mistake<T>& b) {
    return Mistake<T>(a.x + b.x);
}

template <class T, class U>
Mistake<T> operator+(const Mistake<T>& a, U const& b) {
    return a + static_cast<Mistake<T>>(b);
}

template <class T, class U>
Mistake<T> operator+(const U& a, Mistake<T> const& b) {
    return static_cast<Mistake<T>>(a) + b;
}

int main()
{
    X a, b;
    Mistake<int> foo = static_cast<Mistake<int>>(a) + static_cast<Mistake<int>>(b);
    foo = Mistake<int>(a) + b;
    foo = a + Mistake<int>(b);
}
like image 34
sehe Avatar answered Nov 15 '22 17:11

sehe


I believe the compiler has a problem with deducing the type of a + b.

You can define:

X operator+(const X & a, const  X & b) {
   return a /* ??? or something else */;
}

if you have any way to tell what the answer to a + b is in terms of X.

like image 30
wroniasty Avatar answered Nov 15 '22 17:11

wroniasty