【问题标题】:C++ Compiler picks wrong overload of output stream operator <<C++ 编译器选择错误的输出流运算符重载 <<
【发布时间】:2016-07-09 06:31:11
【问题描述】:

我有一个非常简单的内联帮助类,称为IpAddress,它有两个运算符

#include <cstdint>
#include <string>
#include <array>
#include <iostream>
#include <sstream>

typedef uint8_t byte;

class IpAddress: public std::array<byte,4>
{
    // ...

    template<class S>
    inline friend S& operator<<(S& left, const IpAddress& right) {
        left.setBytes(right.data(), 4);
        return left;
    }

    inline friend std::ostream& operator<< (std::ostream& left, const IpAddress& right) {
        // do stuff eligible for an ostream
        return left;
    }

    inline operator std::string() {
        std::stringstream stream;
        stream << *this;
        return stream.str();
    }
}

如您所见,还有一个运算符将地址转换为字符串。不幸的是,里面的

error: C2039: 'setBytes' : is not a member of 'std::basic_stringstream&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt;&gt;'

为什么编译器(在这种情况下为 MSVC)选择了错误的重载和非工作体?为什么不是“更专业”的 std::ostream 运算符?我该如何改变这种行为?谢谢!

【问题讨论】:

  • 标准容器并不是真的要被继承(例如没有虚拟析构函数),而是使用composition。另一篇相关文章about composition over inheritance.
  • 另外(也与您的问题无关)您的设计存在缺陷。不仅在继承方面(IPv4 地址真的是 signed 字节的数组吗?),而且您忘记处理 IPv6 地址。如果有的话,你可以有一个抽象的IPAddress 基类,然后有一个带有IPv4AddressIPv6Address 的继承树。然后有一个工厂函数来检测(以某种方式)正确的类型并创建正确的地址对象并将其作为指向基类的指针返回。
  • 最后是一个小问题...如果您在类定义中定义类似的成员函数,默认情况下它们是inline,因此您不需要指定该关键字。
  • 感谢您的评论。至于组成:我会相应地改变结构。它是一个 unsigned 字节数组。 IPv4 是唯一的用例。在使用来自不同编译单元的未明确声明的内联函数时,我曾经遇到过链接器抱怨多个符号定义的问题,因此我开始将所有内容都声明为内联;)

标签: c++ operator-overloading operators overloading


【解决方案1】:

您没有发布完整的可编译示例,因此我看不到调用站点。

但是,问题很可能是因为您要流式传输的内容不是完全ostream。它可能源自ostream。在这种情况下,template T 会更好。

编辑:

除了风格的 cmets(他们是对的,你最好听一听),这里有一个解决方法:

#include <iostream>
#include <utility>
#include <cstdint>
#include <array>
#include <sstream>


template<class T> static constexpr bool IsAnOstream = std::is_base_of<std::decay_t<T>, std::ostream>::value;

using byte = std::uint8_t;

struct IpAddress: public std::array<byte,4>
{
    // ...

    template<class S, std::enable_if_t<not IsAnOstream<S>>* = nullptr>
    friend S& operator<<(S& left, const IpAddress& right) {
        left.setBytes(right.data(), 4);
        return left;
    }

    friend std::ostream& operator<< (std::ostream& left, const IpAddress& right) {
        // do stuff eligible for an ostream
        return left;
    }

    /* 
     * this is a bad idea - it can lead to all kinds of confusion
     *
    inline operator std::string() {
        std::stringstream stream;
        stream << *this;
        return stream.str();
    }
     */
};

// this is better - it's less surprising.
std::string to_string(const IpAddress& r)
{
    std::stringstream stream;
    stream << *this;
    return stream.str();
}


struct MyStream
{
    void setBytes(const uint8_t* p, size_t len) {}
};

int main()
{
    IpAddress a;
    MyStream s;

    std::cout << a;
    s << a;

    return 0;
}

【讨论】:

  • 谢谢,我添加了必要的includes/typedef,现在应该可以编译了。
  • 正确,在这种情况下是stringstream。为什么模板更匹配?如何改用ostream 版本?
  • @DevCybran 更新了一个修复程序,应该可以让你继续前进。
  • 一个更好的匹配示例是 stringstream 是从 ostream 派生的,因此考虑到 ostream 重载需要“一步转换” - 从 stringstream 到 ostream。但是,模板 T 可以是任何东西 - 包括字符串流。这需要零“转换步骤”。因此 T(当 T 被推断为字符串流时)是更好的匹配。因此选择了模板化重载。
  • 感谢您的解释!由于编译器错误,我将条件模板转换为template&lt;class S, std::enable_if_t&lt;!(std::is_base_of&lt;std::decay_t&lt;S&gt;, std::ostream&gt;::value)&gt;* = nullptr&gt;。但是,stringstream 仍然通过 :(
猜你喜欢
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 1970-01-01
相关资源
最近更新 更多