简体   繁体   中英

How to provide the definition of a friend operator in a nested class of a template class?

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?

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 .

Here is a satisfactory workaround. I didn't want to implement the operator in the class for various reasons (definitions not available at that point, some explicit template instantiation going on here) but I can live with the following:

struct Bob
{
    struct Dave
    {
        Tim t{};

        static bool LessThan(const Dave& a, const Dave& b);

        friend bool operator < (const Dave& a, const Dave& b)
        {
            return LessThan(a, b);
        }
    } d;
};

Now the static function LessThan can be implemented out of the class in the normal way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM