0

I always thought that with templated functions, no implicit conversion may happen and the types of the arguments must exactly match the templated types of the parameters, or else the template argument deduction will fail.

Well, it seems I was mistaken.

Consider the following snippet:

#include <iostream>
#include <type_traits>

template <class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
class myInt
{
    T val;
public:
    myInt(T val) : val(val) {}

    template <class U, typename = typename std::enable_if<std::is_integral<U>::value>::type>
    friend bool operator < (myInt<T> const &t, myInt<U> const &u)
    {
        return t.val < u.val;
    }
};

int main()
{
    myInt<int> i = 5;
    myInt<int> j = 6;

    // std::cout << (i < 6) << std::endl; // gives out compiler errors
    std::cout << (5 < j) << std::endl; // works!
}

I'm not sure why does the second std::cout << (5 < j) work. Implicit conversion must have happened here, which I thought was prohibited. And I'm even less sure why doesn't the std::cout << (i < 6) work if the former one does!

Edit: Compiler errors for std::cout << (i < 6):

test.cpp: In function ‘int main()’:
test.cpp:23:21: error: no match for ‘operator<’ (operand types are ‘myInt<int>’ and ‘int’)
     std::cout << (i < 6) << std::endl; // gives out compiler errors
                     ^
test.cpp:23:21: note: candidate is:
test.cpp:12:17: note: template<class U, class> bool operator<(const myInt<int>&, const myInt<T>&)
     friend bool operator < (myInt<T> const &t, myInt<U> const &u)
                 ^
test.cpp:12:17: note:   template argument deduction/substitution failed:
test.cpp:23:23: note:   mismatched types ‘const myInt<T>’ and ‘int’
     std::cout << (i < 6) << std::endl; // gives out compiler errors

1 Answers1

1

The reason this is "assymetrical" is that there is a fundamental difference between U and T in this:

template <class U, typename =
          typename  std::enable_if<std::is_integral<U>::value>::type>
friend bool operator < (myInt<T> const &t, myInt<U> const &u);

operator< is a template, and U is a parameter to this template. But T is not a parameter to this template. T is actually a parameter to a different template, myInt, and is therefore fixed (to T=int) in this context.

In effect, you have this:

template <class U, /*SFINAE stuff*/>
friend bool operator < (myInt<int> const &t, myInt<U> const &u);

Given (5 < j) it does know how to convert 5 to a myInt<int> const & for t. And it can easily pass j (a myInt<int>) to myInt<U> const &u by inferring U = `int.

But (j<6) won't work, because it doesn't know how to pass 6 to a template<class U> ..... myInt<U> const &u. In particular, how can it guess which U will create a suitable myInt<U> with the necessary constructor? Perhaps myInt<int> has a string constructor, and myInt<string> has an int constructor! The compiler can't know.


I think you could instead do this:

/*   *NOT* a template */
friend bool operator < (myInt<T> const &t, myInt<T> const &u);
                         T instead of U ~~~~~~~~~^
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • I just edited the end of my question, as my proposed solution didn't work. Anyway, I guess the bigger question is, what does the OP want exactly? To be able to compare two `myInt>` of different type? Or, to compare a `myInt>` to a plain `int`, converting the latter, so that both parameters are the same type? – Aaron McDaid Aug 07 '15 at 11:26