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*