StackIT
StackIT

Reputation: 1200

Template and operator overloading

I was going through code... I found a template class which is declared as shown below.

template <class T> 
class tType 
{
public:
    T value;
    T operator=(T val){ value = val; return value; }
    tType<T> operator=(tType<T> &val){ value = val.value; return *this; }
    operator T(){ return value; }
};

I also found operator overloaded like below...

******1******

template <class T>
bool operator==(T& val, tType<T>& tval) { return(val == tval.value); }

I didn't understand when the above operator gets called... cos when I did...

int main(void)
{
    tType<int> x;
    x = 5;
    if (5 == x)
    {
        cout << "Both are equal" << endl;
    }
    return 0;
}

The above said operator was not getting called... and was still working... I also wrote below code for testing and the below code snippet is getting called...

template<class T>
bool operator==(int x, tType<T>& val) { return (x == val.value); }

I wasn't to know who to call *****1*****

Upvotes: 2

Views: 108

Answers (3)

Tony Delroy
Tony Delroy

Reputation: 106236

The == operator attempts to bind non-const references to the arguments - values like "5" are not amenable to that... the parameters should be const:

template <class T>
bool operator==(const T& val, const tType<T>& tval) { return val == tval.value; }

As that will still only support e.g. 5 == my_tType and not my_tType == 5, you may want to also provide...

template <class T>
bool operator==(const tType<T>& tval, const T& val) { return val == tval.value; }

...and for my_tType1 == my_tType2...

template <class T>
bool operator==(const tType<T>& tval1, const tType<T>& tval2) {
    return tval1.value == tval2.value; }

This gives you maximum control of the comparisons. An alternative is to allow implicit construction of a tType from a T argument, but implicit construction and conversion operators are generally discouraged these days - they can lead to ambiguities and accidental creation of temporaries that can be hard to debug. A topic for another question....

When you say "The above said operator was not getting called... and was still working" - what was actually happening was that tType::operator T() (which returns value;) was being implicitly called, returning an int that could be compared - as an int - to 5. Hence - you had a comparison, but not using the custom operator==. This is a normal result of finding the "best match" for a function call.

Upvotes: 2

R Sahu
R Sahu

Reputation: 206717

Change the function

template <class T>
bool operator==(T& val, tType<T>& tval) { return(val == tval.value); }

to

template <class T>
bool operator==(T const& val, tType<T> const& tval) { return(val == tval.value); }

or

template <class T>
bool operator==(T val, tType<T> tval) { return(val == tval.value); }

Your function was not getting called since 5 cannot be converted to int&.

Upvotes: 1

NHDaly
NHDaly

Reputation: 8076

It's right that the reason your operator== isn't called is because of the missing const identifier. In this case, the literal 5 is unable to be passed as a variable reference for the paramater T& val. But, that's not quite the whole story.

As you point out, the code still compiles and runs even though the function isn't being called.

The reason your code still works is because of the conversion operator: operator T(){ return value; }.

A conversion operator allows implicit casts from one type to another. For example:

struct Number {
    Number(int x) : value(x) { }
    operator int() { return value; }
private:
    int value;
};

int main() {

    Number n(5);
    int x = n;          // 5  -- operator int() at work
    int y = n + x;      // 10 -- operator int() at work
    Number z = x + y;   // 15 -- Number(int) at work

    cout << x <<" "<< y <<" "<< z << endl;
    return 0;
}

Here, operator int() allows n to convert into an int for assignment to x and to convert to an int for addition with x (the resultant int is then assigned to y), while the unary constructor Number(int) allows the result of x + y to convert back to a Number.

In your code, 5 == x still compiles because the tType<int> x is converted into an int via its operator int() member function, and then that resultant int is compared against the literal 5.


However, this probably isn't a very good design for a class. For example, in order to compare two tType<T>s, a cast into T will be required for one of them.

template <class T>
bool operator==(const T& val, const tType<T>& tval) {
    cout << "operator==(const T& val, const tType<T>& tval) was called." << endl;
    return(val == tval.value);
}

//...

cout << ( tType<int>() == tType<int>() ) << endl;   // calls operator==(const T&, const tType<T>&)

You would rather have an operator== for two tType<T>s.

Another even bigger problem is that this is not symmetric:

cout << (x1 == 5) << endl;      // calls operator==(int,int)   (the built-in function)

Yikes!


To address all of these, there is a very straightforward design solution. This is taken from Scott Meyers' book Effective C++ Third Edition (a phenomenal book).

Instead of converting back to a T with a conversion operator, the class should include an implicit unary constructor to convert T's up to tType<T>s, and should declare exactly one operator== for tType<T>s:

template <class T> 
class tType 
{
public:
    tType<T>(const T &val) : value(val) { }       // unary non-explicit constructor
    tType<T> operator=(const tType<T> &val){ value = val.value; return *this; } // only need one

    T val() { return value; }
private:
    T value;          // Note also value is private (encapsulation)
};

template<class T>
bool operator==(const tType<T>& a, const tType<T>& b) { return ( a.val() == b.val() ); }

The important changes:

  1. Added a constructor that takes a single value; this allows converting Ts into tTypes.
  2. Removed the assignment operator taking Ts (because now we can convert them to tTypes.
  3. Only have one equality operator and it takes two const tType<T>&s, meaning operator== can handle any combination of Ts and tType<T>s (or, even better, anything convertible into either of those).

Upvotes: 3

Related Questions