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

问题描述 投票:1回答:1

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

注意:我听说过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
c++
1个回答
1
投票

在阅读了关于meta的question and answer之后,并根据评论的建议,我正在我自己的问题中移动一些“更新”并正式发布它们作为自我回答。我这样做是希望这将有助于将来碰巧遇到同样问题的人。谢谢!

正如@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"的顺序如何,用户代码都将编译。

© www.soinside.com 2019 - 2024. All rights reserved.