C ++有一个很好的习惯用法,允许用户编写自动满足运算符之间某些关系的类。例如,这允许定义operator==
而不是定义operator!=
。这就是Boost.Operators背后的想法。
这是一个例子:
template<class Self> // this class is defined only once
struct equally_comparable{
friend bool operator!=(Self const& s1, Self const& s2){return !(s1==s2);}
};
可以反复使用此类来强制==
和!=
之间的一致逻辑(并避免错误)
struct A : equally_comparable<A>{ //
int value;
A(int v) : value(v){}
friend bool operator==(A const& a1, A const& a2){return a1.value == a2.value;}
};
int main(){
A a1{4};
A a2{4};
assert(a1 == a2);
A a3{5};
assert(a1 != a3); // uses automatically generated operator !=
}
现在,我想更进一步,并有一个类似于equally_comparable
的类,并定义其他函数。例如,如果定义了operator==
,则定义operator!=
(如上所述),但也反之。
第一次天真的尝试有效
template<class Self>
struct equally_comparable{
friend bool operator!=(Self const& s1, Self const& s2){return !(s1==s2);}
friend bool operator==(Self const& s1, Self const& s2){return !(s1!=s2);}
};
因为只需要在struct A
(operator==
或operator!=
)中定义两个函数中的一个。然而,它是危险的,因为如果忘记在A
中定义任一运算符,则存在无限递归(以及运行时段错误)。它看起来也很脆弱。
是否可以对此进行改进并检测在编译时在派生类中至少定义了一个?或者更一般地说,有一个通用的方法来生成一个生成缺失运算符的类吗?(即超出Boost.Operators的步骤)。
答案 0 :(得分:0)
我会使用SFINAE来在编译时检查比较器并相应地选择它们。
所以这可以如下工作:
equal_comparable_tag
enable_if
保护,因为功能匹配非模板化版本是首选。警卫检查该类是否来自标记类。#include <type_traits>
#include <iostream>
struct equal_comparable_tag {};
struct tmp : public equal_comparable_tag {
friend bool operator==(const tmp &, const tmp &) {
std::cout << "baz";
return false;
}
};
struct tmp2 : public equal_comparable_tag {
friend bool operator!=(const tmp2 &, const tmp2 &) {
std::cout << "baz";
return false;
}
};
template<typename T>
typename std::enable_if<
std::is_base_of<equal_comparable_tag, T>::value,
bool
>::type
operator!=(const T &, const T&) {
std::cout << "foo";
return true;
}
template<typename T>
typename std::enable_if<
std::is_base_of<equal_comparable_tag, T>::value,
bool
>::type
operator==(const T &, const T&) {
std::cout << "bar";
return true;
}
int main(int argc, char** argv) {
tmp a,b;
tmp2 c,d;
if (a != b) {} // foo
if (a == b) {} // baz
if (c != d) {} // baz
if (c == d) {} // bar
}
请注意,自动函数签名检查也可以完成(通常通过类型特征和函数指针参见this article),但要复杂得多。如果要检查是否实现了两个功能,则需要这样做。还要注意,您需要检查所有可能的组合。
即:如果你有operator >
和operator ==
,你必须检查:
如果某人使用隐式转换,则这些检查会失败,例如:
struct tmp4 {
tmp4(int) {
}
tmp4() {}
operator int() const {
return 10;
}
bool operator!=(int) const {
std::cout << "baz";
return false;
}
};
int main(int argc, char** argv) {
tmp4 g, h;
if (g != h) {} // compiles!
}