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

时间:2017-01-17 14:34:39

标签: 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;
}

错误是&#34;二进制&#39; ==&#39;:找不到运算符,它接受类型&#39; otherns :: Property&#39;的左手操作数。 (或者没有可接受的转换)&#34;这发生在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;
}

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

实际上otherns来自第三方库的标题,因此我无法将操作符放入该标题中。

3 个答案:

答案 0 :(得分:1)

规则相当复杂,而我自己并没有完全掌握它们,但让我们看看我们是否可以做出它们的正面或反面(我认为我们可以):

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{}); 
}

找不到这个原因很简单: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{}); 
}

这有用吗?是的,确实如此,它编译并调用我们的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{});
}

然后(2)在ns中隐藏(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{}); 
}

将编译并静默调用(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{}); 
}

这里发生的事情是ADL启动并从{{​​1}}带来(1),现在nx被认为是(1)。但(2)(1)更专业,因此正确选择了(2)

如果您无法控制(1)并且无法在那里添加运算符,那么我建议的是使用callables而不是依赖运算符。 E.g而不是namespace nx使用std::find和您自己的谓词(lambda),您可以在其中精确控制要调用的方法/运算符。当我说“确切”时,我指的是完全:即std::find_if(或您声明的任何名称空间),而不是::operator==(x1, x2)

您可以阅读Herb Sutter撰写的这篇精彩文章Namespaces & Interface Principle

答案 1 :(得分:1)

只需在命名空间operator==中声明otherns(查找将在命名空间范围内找到它)

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

Working code

您可以在第三方库的单独标题中执行此操作。

答案 2 :(得分:0)

您在全局命名空间中定义了operator==(可能误导了误导)。它不会通过参数依赖查找在那里找到。

运算符应声明在与其参数名称相同的命名空间中:

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();
    }
}

这个小改动使你的例子能够干净地编译。