This works:
template<class Tim>
struct Bob
{
struct Dave
{
Tim t{};
friend bool operator < (const Dave& a, const Dave& b)
{
return a.t < b.t;
}
} d;
};
This does not work:
template<class Tim>
struct Bob
{
struct Dave
{
Tim t{};
friend bool operator < (const Dave& a, const Dave& b);
} d;
};
template<class Tim>
bool operator < (const typename Bob<Tim>::Dave& a, const typename Bob<Tim>::Dave& b)
{
return a.t < b.t;
}
When I try to use it in a map for example, I get linker errors:
1>ConsoleApplication1.obj : error LNK2019: unresolved external symbol "bool __cdecl operator<(struct Bob<int>::Dave const &,struct Bob<int>::Dave const &)" (??M@YA_NABUDave@?$Bob@H@@0@Z) referenced in function "public: bool __thiscall std::less<struct Bob<int>::Dave>::operator()(struct Bob<int>::Dave const &,struct Bob<int>::Dave const &)const " (??R?$less@UDave@?$Bob@H@@@std@@QBE_NABUDave@?$Bob@H@@0@Z)
.
int main()
{
std::map<Bob<int>::Dave, int> v;
v[{}];
}
How can I define this operator correctly outside the class?
Templates themselves can't be friends, though the closest to what this could mean is to make all specializations of one template friends of all specializations of another template (then friend declaration has to be templated - see the accepted answer).
Friends aren't in the class's scope, and they aren't called using the member-selection operators (. and ->) unless they're members of another class. A friend function is declared by the class that is granting access. The friend declaration can be placed anywhere in the class declaration.
template <typename T> ... This means exactly the same thing as the previous instance. The typename and class keywords can be used interchangeably to state that a template parameter is a type variable (as opposed to a non-type template parameter).
For normal code, you would use a class template when you want to create a class that is parameterised by a type, and a function template when you want to create a function that can operate on many different types.
You would normally do such a thing by forward-declaring the template class and the friend function and then providing a specialization within the class definition. However, in this case it is not as easy - having a dependent type puts the Tim
class in a non-deduced context, so the deduction will fail. However, there's a way around it:
#include <iostream>
#include <type_traits>
#include <map>
template<class T>
struct Bob;
template<typename T, typename>
bool operator < (const T& a, const T& b);
struct DaveTag {};
template<class Tim>
struct Bob
{
struct Dave : DaveTag
{
Tim t{};
friend bool operator < <Bob<Tim>::Dave, void>(const typename Bob<Tim>::Dave& a, const typename Bob<Tim>::Dave& b);
} d;
};
template<typename T, typename = typename std::enable_if<std::is_base_of<DaveTag, T>::value>::type>
bool operator < (const T& a, const T& b)
{
return a.t < b.t;
}
struct X {
double t;
};
int main()
{
std::map<Bob<int>::Dave, int> v;
v[{}];
// This won't work
// X x, y;
//bool b = x < y;
}
Basically, what I've done here is let the compiler deduce the full Bob<Tim>::Dave
as the template parameter for operator<
. However, clearly a simple definition would allow for any types to be deduced for T
potentially leading to some difficult-to-understand problems. To avoid it I added a small tag class DaveTag
which allows to prevent instantiations of our very generic operator<
for anything but Dave
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With