在类设计中查找外部命名空间中的重载运算符

Look up overloaded operators in outer namespace in class design

本文关键字:命名空间 重载 运算符 外部 查找      更新时间:2023-10-16

如何让命名空间中的包装类知道在外部/全局命名空间中,它包装的对象可能存在重载运算符?

注意:我听说过ADL或Koenig查找,但我遇到了一个真正的问题。

真正的设计问题

我想设计一个仅标题库。假设我把所有东西都放在命名空间my.与此问题相关的部分可以简化为类似于模板包装器item

// my.hpp
#include <iostream>
namespace my
{
template<typename T>
struct item
{
T thing;
item(T t) : thing(t) {}
};
template<typename T>
std::ostream & operator<<(std::ostream & os, const item<T> & it)
{
os << it.thing;
return os;
}
}

有了item我想要实现的是:

  1. item<T>包装一个T对象(包含用户提供的T对象(
  2. 如果operator<<(std::ostream &, const T &)没有在<iostream>中定义,那么我假设用户重载了operator<<(std::ostream &, const T &),我希望operator<<(std::ostream &, const item<T> &)调用它。

具体的用户示例

考虑一组用户代码,该代码为T = std::vector<double>执行此操作

// user.hpp
// #include guard omitted
#include <iostream>
#include <vector>
std::ostream & operator<<(std::ostream &, const std::vector<double> &);

// user.cpp
#include <iostream>
#include <vector>
std::ostream & operator<<(std::ostream & os, const std::vector<double> & v)
{
for (const auto & e : v)
os << e << " | ";
return os;
}
int main()
{
std::vector<double> vec = {3.14, 2.83};
std::cout << my::item<std::vector<double>>(vec);
}

现在,如果用户将

#include "user.hpp"
#include "my.hpp"

user.cpp开始时,一切都会很好,g++ user.cpp会按预期编译。

但是,如果用户更改了订单并放置

#include "my.hpp"
#include "user.hpp"

编译器将生成一个错误,指出

my.hpp: In function 'std::ostream& my::operator<<(std::ostream&, const my::item<T>&)':
my.hpp:15:23: error: '::operator<<' has not been declared

当然,我不希望结果取决于#include的顺序。

我的问题是:作为命名空间my和包装器item<T>的设计者,我可以做些什么来myitem<T>,以便item<T>可以正确发现并调用operator<<(std::ostream, const T &)(如果它是由用户提供的(?

谢谢你的时间!

更新:供您参考,g++ --version退货

g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.3.0

在阅读了元维基上的问题和答案,并遵循评论的建议后,我在自己的问题中移动了一些"更新",并将它们正式发布为自我答案。我这样做是希望这将帮助将来碰巧遇到相同问题的人。谢谢!

正如@xskxzr在注释中正确指出的那样,用户代码中存在一些不好的东西。具体来说,

声明其参数都是std实体的函数/操作是不好的,因为您无法将此类声明添加到 std 中以使用 ADL

在这种情况下,问题出在用户方面,而不是设计师。

现在,如果用户进行了更改

// user.hpp
#include <iostream>
#include <vector>
// CHANGE: (privately) inherit from std::vector<double>, rather than overload directly
struct DoubleVector : private std::vector<double>
{
using std::vector<double>::vector;
friend
std::ostream & operator<<(std::ostream &, const DoubleVector &);
};
std::ostream & operator<<(std::ostream &, const DoubleVector &);

// user.cpp
#include "my.hpp"
#include "user.hpp"
#include <iostream>
#include <vector>
// CHANGE: use a user-defined DoubleVector class
std::ostream & operator<<(std::ostream & os, const DoubleVector & c)
{
for (const auto & e : c)
os << e << " | ";
return os;
}
int main()
{
DoubleVector vec = {3.14, 2.83};
std::cout << my::item<DoubleVector>(vec);
}

然后,无论#include "my.hpp"#include "user.hpp"的顺序如何,用户代码都将进行编译。