C ++中非常自动的运算符生成器

时间:2015-07-01 08:44:59

标签: c++11 operators sfinae regular-type

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 Aoperator==operator!=)中定义两个函数中的一个。然而,它是危险的,因为如果忘记在A中定义任一运算符,则存在无限递归(以及运行时段错误)。它看起来也很脆弱。

是否可以对此进行改进并检测在编译时在派生类中至少定义了一个?或者更一般地说,有一个通用的方法来生成一个生成缺失运算符的类吗?(即超出Boost.Operators的步骤)。

1 个答案:

答案 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 ==,你必须检查:

  • 具有会员功能&gt;和成员函数==
  • 具有免费功能&gt;并具有自由功能==
  • 具有免费功能&gt;和成员函数==
  • 哈希成员函数&gt;和自由功能==

如果某人使用隐式转换,则这些检查会失败,例如:

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!
}