C++ 类外定义的友元函数在模板上的隐式转换查找失败

C++ 类外定义的友元函数在模板上的隐式转换查找失败,c++,templates,c++11,C++,Templates,C++11,下面的代码 #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 =

下面的代码

#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);
}
#包括
#包括
模板
结构foo{
foo(std::nullptr_t){
//友元布尔运算符==(foo-lhs,foo-rhs){返回true;}
模板
友元布尔运算符==(foo-lhs,foo-rhs);
};
模板
内联布尔运算符==(foo-lhs,foo-rhs){返回true;}
int main(){
foop=nullptr;
断言(p==nullptr);
}
无法编译,并显示错误消息

foo.cpp:18:5:错误:与“
p==nullptr
”中的“
operator==
”不匹配
foo.cpp:18:5:注:候选人为:
foo.cpp:14:13:注意:
模板bool操作符==(foo,foo)

foo.cpp:14:13:注意:模板参数扣除/替换失败:
foo.cpp:18:5:注意:类型不匹配“
foo
”和“
std::nullptr\t

但是,如果我在类中使用该定义,代码将按预期工作

让我说,我理解错误消息:无法为
nullptr
类型推导模板参数
T
(顺便说一下,
decltype(*nullptr)
不编译)。此外,这可以通过在类中定义函数来解决

但是,出于一致性的原因(我需要在外部定义其他友元函数),我希望在类之外定义此函数


是否有“技巧”使函数的类外定义起作用?

有三种可能的选择

  • 声明并定义rhs类型为
    std::nullptt\u t

inline bool operator ==(foo<T> lhs, std::nullptr_t rhs) { return true; }
assert(p == foo<int>(nullptr));
inline bool operator ==(foo<T> lhs, void *rhs) {         
    if (rhs == nullptr) 
        return true; 
    else
        return false;
    }
  • 声明并定义一个rhs类型为
    void*

inline bool operator ==(foo<T> lhs, std::nullptr_t rhs) { return true; }
assert(p == foo<int>(nullptr));
inline bool operator ==(foo<T> lhs, void *rhs) {         
    if (rhs == nullptr) 
        return true; 
    else
        return false;
    }
inline bool操作符==(foo-lhs,void*rhs){
如果(rhs==nullptr)
返回true;
其他的
返回false;
}

Abhijit已经为您提供了基本的解决方案,但我想我会详细阐述一下,因为这是一个有趣的问题

如果在模板类中声明友元函数,如下所示:

template <typename T>
struct A {
  friend void f(A);
};
模板
结构A{
f(A);
};
那么您要说的是,任何以A为参数的非模板函数f都是A的朋友。因此您需要单独定义这些函数:

inline void f(A<int>) {...}
inline void f(A<float>) {...}
// etc.
inline void f(A){…}
内联空f(A){…}
//等等。
尽管在类中定义它是一种快捷方式

在这种情况下,无法创建一个为每个T定义友元f(a)的模板,因为您已经声明友元是非模板函数。事实上,它是一个非模板函数,这使得它在您的示例中可用,因为当编译器查找匹配函数时,非模板函数比模板函数允许更多的转换

有一个相当普遍的解决方法,尽管它有点混乱。您可以定义其他模板函数来处理nullptr,或者您可能抛出的任何其他模板函数,并将其推迟到主函数:

template <typename T>
inline bool operator ==(foo<T> lhs, std::nullptr_t rhs)
{
  return lhs==foo<T>(rhs);
}
模板
内联布尔运算符==(foo-lhs,std::nullptr\u t-rhs)
{
返回lhs==foo(rhs);
}
为了对称,您可能需要两种方法:

template <typename T>
inline bool operator ==(std::nullptr_t lhs,foo<T> rhs)
{
  return foo<T>(lhs)==rhs;
}
模板
内联布尔运算符==(std::nullptr\t lhs,foo rhs)
{
返回foo(lhs)=rhs;
}
另一方面,您定义friend函数的方式使
操作符==(foo,foo)
成为
foo
的朋友,即使U和T不是相同的类型。这在实践中可能不会有太大的区别,但有一种技术上更好的方法可以做到这一点。它涉及到向前声明模板函数,然后使模板参数的专门化成为朋友

以下是一个完整的示例:

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);
}
模板结构foo;
模板
内联布尔运算符==(foo-lhs,foo-rhs);
模板
结构foo{
foo(std::nullptr_t){
友元布尔运算符==(foo-lhs,foo-rhs);
};
模板
内联布尔运算符==(foo lhs,foo rhs)
{
返回true;
}
模板
内联布尔运算符==(foo-lhs,std::nullptr\u t-rhs)
{
返回lhs==foo(rhs);
}
模板
内联布尔运算符==(标准::null\u ptr\u t lhs,foo rhs)
{
返回foo(lhs)=rhs;
}
int main(){
foop=nullptr;
断言(p==nullptr);
断言(nullptr==p);
断言(p==p);
}

我想你指的是“成员函数”和“非成员函数”,而不是“内联”和“非内联”。在您的示例中,您有一个内联的非成员函数,我想您是说,如果您将其设为成员函数,它就可以工作。@VaughnCato朋友是否算作成员函数?我不确定。它们当然不在类的命名空间中。友元函数将是非成员函数,但它们可能是内联的,也可能不是内联的,它们可能是在类的内部或外部定义的。在您的情况下,您有一个在类外部定义的内联函数。@ildjarn不编译,即使它会编译,也不会改变错误的原因。这两个函数都不具有吸引力。特别是,第一种方法并不能真正解决问题,因为
0
(又称
NULL
)和
(void*)0
也会出现同样的情况。第二个将库问题转移给客户。这违反了Scott Meyers使库难以错误使用的原则。第一个选项适用于0或NULL,但不适用于(void*)0。但我不确定您是否真的希望它为(void*)0工作。您的类没有定义一个构造函数,该构造函数采用void*@KonradRudolph:As@VaughnCato,正如前面提到的,第一个示例适用于0和NULL,但您知道它不适用于(void*)0。如果您确实希望接受(void*)0,并在类中适当地定义构造函数,您也可以将rhs定义为
void*