如何在不同的命名空间中指定重载运算符?

Pet*_*ull 5 c++

我遇到了C++标准库的问题.下面的例子没有编译:(注意这是为了做一个最小的例子,所以没有多大意义,因为它是)

#include <algorithm>
#include <string>
#include <vector>

namespace otherns {

class Property {
public:
  const std::string &getName() const { return m_name; }

private:
  std::string m_name;
};
}

bool operator==(const otherns::Property &a, const otherns::Property &b) {
  return a.getName() == b.getName();
}

/* Merge, second takes priority */
std::vector<otherns::Property>
merge_props(const std::vector<otherns::Property> &xs,
            const std::vector<otherns::Property> &ys) {
  std::vector<otherns::Property> ans = ys;
  for (const auto &x : xs) {
    if (std::find(ans.begin(), ans.end(), x) == ans.end()) {
      ans.push_back(x);
    }
  }
  return ans;
}
Run Code Online (Sandbox Code Playgroud)

错误是"二进制'==':没有找到运算符,它采用类型为'otherns :: Property'的左手操作数(或者没有可接受的转换)",它出现在执行的某处std::find.这是与MSVC,但我也尝试使用clang和gcc,结果相似.

以下代码确实有效:

std::vector<otherns::Property>
merge_props(const std::vector<otherns::Property> &xs,
            const std::vector<otherns::Property> &ys) {
  std::vector<otherns::Property> ans = ys;
  for (const auto &x : xs) {
    if (std::find_if(ans.begin(), ans.end(), [&x](const otherns::Property &y) {
          return x == y;
        }) == ans.end()) {
      ans.push_back(x);
    }
  }
  return ans;
}
Run Code Online (Sandbox Code Playgroud)

我想这与ADL/Koenig查找有关,但我真的不明白为什么operator==找不到我.如果我想使用第一个更简单的find函数形式,那么最好的解决方案是什么?

实际上,otherns它来自第三方库的标题,因此我无法将我的运算符放入该标题中.

bol*_*lov 2

规则相当复杂,我自己也没有完全掌握它们,但是让我们看看我们是否可以弄清楚它们(我认为我们可以):

namespace nx {
struct X {};
}

namespace ns {    
auto foo(nx::X x1, nx::X x2) { return x1 == x2; }
// error: no match for 'operator==' (operand types are 'nx::X' and 'nx::X')
}

auto operator==(nx::X, nx::X) { return true; }

auto global_foo()
{
  return ns::foo(nx::X{}, nx::X{}); 
}
Run Code Online (Sandbox Code Playgroud)

没有找到它的原因很简单:operator==在使用之前没有声明。与 ADL 无关。到目前为止,一切都很好。我们明白这一点。让我们修复它:

namespace nx {
struct X {};
}

auto operator==(nx::X, nx::X) { return true; }

namespace ns {
auto foo(nx::X x1, nx::X x2) { return x1 == x2; }
}

auto global_foo()
{
  return ns::foo(nx::X{}, nx::X{}); 
}
Run Code Online (Sandbox Code Playgroud)

这有效吗?是的,确实如此,它编译并调用我们的operator==. 这是正确的解决方案吗?不!。因为如果我们添加这个:

namespace nx {
struct X {};
}

auto operator==(nx::X, nx::X) { return true; } // (1)

namespace ns {

template <class T> auto operator==(T, int) { return false; } // (2)

auto foo(nx::X x1, nx::X x2) { return x1 == x2; }
// error: no match for 'operator==' (operand types are 'nx::X' and 'nx::X')
}

auto global_foo()
{
  return ns::foo(nx::X{}, nx::X{});
}
Run Code Online (Sandbox Code Playgroud)

然后,ns 中的 (2) 会隐藏全局命名空间中的 (1),即使 (1) 更合适。这称为名称隐藏,并且不以任何方式涉及 ADL。

更糟糕:

namespace nx {
struct X {};
}

auto operator==(nx::X, nx::X) { return true; } // (1)

namespace ns {

template <class T> auto operator==(T, T) { return false; } // (2)

auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // calls (2)
}

auto global_foo()
{
  return ns::foo(nx::X{}, nx::X{}); 
}
Run Code Online (Sandbox Code Playgroud)

将编译并静默调用(2)而不是我们的运算符(1)

对于现实世界的上下文,可以将其视为namespace ns名称空间std和在 . 内部声明的任何运算符std。你的帖子里也写到了你的情况。

正确的解决方案是:

namespace nx {
struct X {};
auto operator==(nx::X, nx::X) { return true; } // (1)
}

namespace ns {

template <class T> auto operator==(T, T) { return false; } // (2)

auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // calls (1)
}

auto global_foo()
{
  return ns::foo(nx::X{}, nx::X{}); 
}
Run Code Online (Sandbox Code Playgroud)

这里发生的是 ADL 启动并带来(1)fromnx和 now(1)被认为是并行的(2)。但(1)比它更专业(2),所以(1)选择正确。

如果您无法控制namespace nx并且无法在那里添加运算符,那么我可以建议是使用可调用对象而不是依赖运算符。例如,不要与您自己的谓词(lambda)一起std::find使用,您可以在其中精确控制要调用的方法/运算符。std::find_if当我说“完全”时,我的意思: ie ::operator==(x1, x2)(或您声明它们的任何命名空间)而不是x1 == x2.


您可以阅读 Herb Sutter 撰写的这篇精彩文章《命名空间和接口原理》的更多内容