Hidden Friends

下面的代码中,Bar 能够隐式转换成 Foo。想要重载 Foo 的等于运算符至少有三种方案:

  1. 重载全局的 == 运算符
  2. 重载 Foo 中的 hidden friend,即 friend bool operator==(Foo const &a, Foo const &b)
  3. 重载 bool operator==(Foo const &a) const

第一种方案会污染全局的名字空间,使得 Bar 和 Bar 之间也能通过转换两个参数进行比较;第三种方案允许第二个参数的隐式类型转换,但要求第一个参数必须是 Foo 类型;第二种方案只要求任一个参数为 Foo 类型,使得 ADL 能够参与找到这个函数,另一个参数则可以通过隐式类型转换来得到。

成员函数的劣势是由于查找方式和 this 指针的存在,函数失去了对称性。全局函数的劣势则是没有用上 ADL 的优势,hidden friends 在这种情况比较好。

https://godbolt.org/z/6sxdno3Yh

#include <iostream>

#define HIDDEN_FEIDEND 1

struct Foo {
    int value{};
#ifdef HIDDEN_FEIDEND
    friend bool operator==(Foo const &a, Foo const &b) {
        return a.value == b.value;
    }
#endif
};

struct Bar {
    int value{};

    // Bar can be converted implicitly to Foo.
    // `explicit` keyword makes the conversion explicit.
    // explicit
    operator Foo() const {
        return Foo{value};
    }
};

#ifndef HIDDEN_FEIDEND
// Normal operator overload that can be found in global scope
bool operator==(Foo const &a, Foo const &b) {
    return a.value == b.value;
}
#endif

int main() {
    Bar a{-4}, b{1};
    Foo c{2};
    // std::cout << std::boolalpha << (a == b) << "\\n"; // ok if no HIDDEN_FEIDEND
    std::cout << std::boolalpha << (a == c) << "\\n"; // ok
    std::cout << std::boolalpha << (c == a) << "\\n"; // ok
}