Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion lookup on template fails for friend function defined outside of class

The following code

#include <cassert>
#include <cstddef>

template <typename T>
struct foo {
    foo(std::nullptr_t) { }
    //friend bool operator ==(foo lhs, foo rhs) { return true; }

    template <typename U>
    friend bool operator ==(foo<U> lhs, foo<U> rhs);
};

template <typename T>
inline bool operator ==(foo<T> lhs, foo<T> rhs) { return true; }

int main() {
    foo<int> p = nullptr;
    assert(p == nullptr);
}

fails to compile with the error message

foo.cpp:18:5: error: no match for 'operator==' in 'p == nullptr'
foo.cpp:18:5: note: candidate is:
foo.cpp:14:13: note: template<class T> bool operator==(foo<T>, foo<T>)
foo.cpp:14:13: note: template argument deduction/substitution failed:
foo.cpp:18:5: note: mismatched types 'foo<T>' and 'std::nullptr_t'

However, if I use the definition inside the class instead, the code works as expected.

Let me say that I understand the error message: the template argument T cannot be deduced for the type of nullptr (incidentally, decltype(*nullptr) doesn’t compile). Furthermore, this can be fixed here by just defining the function inside the class.

However, for reasons of uniformity (there are other friend functions which I need to define outside) I would like to define this function outside of the class.

Is there a “trick” to make an outside of class definition of the function work?

like image 378
Konrad Rudolph Avatar asked Apr 29 '12 20:04

Konrad Rudolph


2 Answers

There are three possible option for you

  • Declare and Define a new friend function with the type of rhs as std::nullptt_t

for ex

inline bool operator ==(foo<T> lhs, std::nullptr_t rhs) { return true; }
  • Or when equating the variable with nullptr, explicitly state the type of nullptr

for ex

assert(p == foo<int>(nullptr));
  • Declare and Define a new friend function with the type of rhs as void *

for ex

inline bool operator ==(foo<T> lhs, void *rhs) {         
    if (rhs == nullptr) 
        return true; 
    else
        return false;
    }
like image 82
Abhijit Avatar answered Nov 20 '22 18:11

Abhijit


Abhijit has already given you the basic solution, but I thought I would expound upon it a bit since this is an interesting problem.

If you declare a friend function inside a template class, like this:

template <typename T>
struct A {
  friend void f(A);
};

Then what you are saying is that any non-template function called f that takes A as a parameter will be a friend of A. So you would need to define these functions separately:

inline void f(A<int>) {...}
inline void f(A<float>) {...}
// etc.

Although defining it inside the class is a shortcut.

In this case, there is no way to make a template that defines the friend f(A) for every T, because you've already stated that it is the non-template function that is the friend. It is the very fact that it is a non-template function that makes it usable in your example, because non-template functions allow more conversions than template functions when the compiler looks for matching functions.

There is a fairly general workaround, although it is a bit messy. You can define other template functions that will handle your nullptr, or whatever else you might throw at it, and just defer it to your main function:

template <typename T>
inline bool operator ==(foo<T> lhs, std::nullptr_t rhs)
{
  return lhs==foo<T>(rhs);
}

You may want to do it both ways for symmetry:

template <typename T>
inline bool operator ==(std::nullptr_t lhs,foo<T> rhs)
{
  return foo<T>(lhs)==rhs;
}

On a separate note, the way you've defined your friend function makes operator==(foo<U>,foo<U>) a friend of foo<T> even when U and T are not the same types. It probably isn't going to make much difference in practice, but there is a technically better way to do this. It involves forward declaring the template function, and then making the specialization for your template parameter be a friend.

Here is a full example:

template <typename> struct foo;

template <typename T>
inline bool operator==(foo<T> lhs,foo<T> rhs);

template <typename T>
struct foo {
    foo(std::nullptr_t) { }

    friend bool operator==<>(foo lhs,foo rhs);
};

template <typename T>
inline bool operator ==(foo<T> lhs,foo<T> rhs)
{
  return true;
}

template <typename T>
inline bool operator ==(foo<T> lhs, std::nullptr_t rhs)
{
  return lhs==foo<T>(rhs);
}

template <typename T>
inline bool operator ==(std::null_ptr_t lhs,foo<T> rhs)
{
  return foo<T>(lhs)==rhs;
}

int main() {
    foo<int> p = nullptr;
    assert(p == nullptr);
    assert(nullptr == p);
    assert(p == p);
}
like image 27
Vaughn Cato Avatar answered Nov 20 '22 17:11

Vaughn Cato