使用模板的多重继承进行C++运算符重载

6

我有一个层次结构,代表了HTTP客户端的一部分,看起来像这样:

typedef list<pair<string, string> > KeyVal;
struct Header { string name; string value; ...};
struct Param { string name; string value; ...};

/* Something that contains headers */
template<typename T> class WithHeaders {
  KeyVal headers;
public:
  virtual T &operator <<(const Header &h) {
    headers.push_back(pair<string, string>(h.name, h.value));
    return static_cast<T&> (*this);
  }
};

/* Something that contains query params */
template<class T> class WithQuery {
    KeyVal query_params;

public:
    virtual T &operator <<(const Param &q) {
      query_params.push_back(pair<string, string>(q.name, q.value));
      return static_cast<T&> (*this);
    }

    const KeyVal &get_query() const {return query_params;}
};

/* Http Request has both headers and query parameters */
class Request: public WithQuery<Request>, public WithHeaders<Request> {...};

所以我希望能够像这样做一些事情:request << Header(name, value) << Param("page", "1")(并且稍后将在相应的Response类中重用WithHeaders)。
我正在尝试编译的代码是:
Request rq = Request("unused", "unused", "unused");
rq << Header("name", "value");

然而,我得到:
test/test_client.cpp:15:30: error: request for member ‘operator<<’ is ambiguous
In file included from test/test_client.cpp:1:0:
test/../client.h:45:16: error: candidates are: 
    T& WithQuery<T>::operator<<(const Param&) [with T = Request]         
    T& WithHeaders<T>::operator<<(const Header&) [with T = Request]

我可能漏掉了些什么,但是在编译时区分ParamHeader似乎非常容易。因此,问题是:

  • 为什么会失败,如何修复?
  • 这是否合理,还是有更简单的设计?

我的错,那是在复制代码时犯的错误(请参见对@Joachim答案的评论)。已修复。 - bereal
1
你使用的编译器是什么?在我看来,这似乎是某种编译器错误,稍微修改一下的示例可以在 clang++ 下编译通过(http://coliru.stacked-crooked.com/a/0749bab057ef385d),但在 g++ 下则失败了(http://coliru.stacked-crooked.com/a/5f8e26c15c188dcf)。 - Filip Roséen - refp
使用g++进行编译,所以这肯定是某个编译器的bug =( - bereal
1
我会深入研究标准,并尝试得出明确的答案,以确定哪个编译器正在执行正确的操作。来自 g++ 的错误消息确实令人困惑,但也许名称隐藏使代码非法(因此它不应该被编译)。 - Filip Roséen - refp
1
g++ 拒绝代码是正确的做法(尽管错误信息有误导性)。如果派生类中不存在一个 名称,但在多个基类中存在,则在进行查找时不应考虑任何一个基类。标准中可以阅读到 10.2/6-7 的确切规则。 - Filip Roséen - refp
显示剩余3条评论
1个回答

5

我认为它应该可以工作,所以很可能是GCC的一个bug。 正如评论中@refp指出的那样,查找实际上是有歧义的,GCC拒绝它是正确的。

这是使它工作的方法:

class Request: public WithQuery<Request>, public WithHeaders<Request> {
public:
    using WithHeaders<Request>::operator<<;
    using WithQuery<Request>::operator<<;
};

Live example


不确定是否是GCC的错误,标准中的继承名称解析可能会认为代码是非法的(但您的修复确实解决了这个问题)。 - Filip Roséen - refp
太棒了,谢谢!但是那具体是什么意思,为什么会有所不同呢? - bereal
1
@bereal 声明 using X 将名称 X 引入声明出现的作用域。换句话说,这里您明确表示 Request 具有成员函数 WithHeaders<Request>::operator<<WithQuery<Request>::operator<<。这简化了名称查找。 - Angew is no longer proud of SO
可能是。我现在没有时间查找名称查找规则[;-)],但我的直觉是它应该找到并且重载分辨率应该选择正确的一个。 - Angew is no longer proud of SO
@Angew 我正在阅读标准,这似乎不是这种情况,如果我正确解释 10.2/6,原始代码就是格式不正确的。我会仔细阅读并查找确定的答案。 - Filip Roséen - refp
1
@Angew 看看 10.2/6-7,程序已经被告知并且 gcc 正确地拒绝了它,尽管(像往常一样)错误消息并没有太大的帮助。 - Filip Roséen - refp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接