9

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?

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 1
    I think you mean "member function" and "non-member function" instead of "inline" and "non-inline". In your example, you have an inline non-member function, and I think you are saying it works if you make it a member function. – Vaughn Cato Apr 29 '12 at 20:46
  • @VaughnCato Do friends count as member functions? I’m not sure of that. They certainly aren’t in the namespace of the class. – Konrad Rudolph Apr 29 '12 at 21:07
  • Friend functions will be non-member functions, but they may or may not be inline, and they may be defined internally or externally to the class. In your case, you have an inline function that is defined externally to the class. – Vaughn Cato Apr 29 '12 at 21:11
  • @ildjarn That doesn’t compile, and even if it would, it wouldn’t change the reason for the error. – Konrad Rudolph Apr 29 '12 at 22:02

2 Answers2

5

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;
    }
Abhijit
  • 62,056
  • 18
  • 131
  • 204
  • Neither is appealing. In particular, the first doesn’t really solve the problem because the same happens with `0` (aka `NULL`) and `(void*)0`. The second carts off a library problem to the client. This is a violation of Scott Meyers’ principle of making a library hard to use wrong. – Konrad Rudolph Apr 29 '12 at 21:09
  • The first option will work for 0 or NULL, although not for (void *)0. I'm not sure you really want it to work for (void *)0 though. Your class doesn't define a constructor that takes a void *. – Vaughn Cato Apr 29 '12 at 21:23
  • @KonradRudolph: As @VaughnCato as already mentioned that the first example would work for 0 and NULL but as you know it wont work for (void *)0. In case you do want to accept (void *)0, and appropriately define the constructor in your class, you can alternatively define the rhs as `void *` and explicitly check if the pointer is a nullptr, see my third example. – Abhijit Apr 29 '12 at 21:44
3

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);
}
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Ah. I tried that last one but I got the syntax wrong (missing `<>`) so it didn’t work. Great. Accepted for the explanation and complete “best practice” example. Small nitpick: I don’t agree that it’s a “stylistic choice” to make `operator ==` symmetrical. It is *required* for a good API. Again, it follows from the principle of least surprise and the guideline of making APIs hard to use wrong. – Konrad Rudolph Apr 30 '12 at 09:12
  • @KonradRudolph: Yean, "stylistic" may not be the best choice of words -- edited. – Vaughn Cato Apr 30 '12 at 12:04