C++ 编译器选择输出流运算符<<的错误重载

C++ Compiler picks wrong overload of output stream operator <<

本文关键字:lt 错误 重载 编译器 选择 运算符 C++ 输出流      更新时间:2023-10-16

我有一个非常简单的内联助手类IpAddress,它有两个重载运算符<lt;将对象序列化为一组自定义二进制流(运算符作为模板函数),或将其输出到std::ostream(非模板运算符函数)。

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

正如您所看到的,还有一个运算符可以将地址转换为字符串。不幸的是,<lt;内部调用选择了错误的运算符<lt;(模板一),导致编译器错误:

error: C2039: 'setBytes' : is not a member of 'std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>'

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

您没有发布一个完整的、可编译的示例,所以我看不到调用站点。

然而,这个问题很可能是因为无论你流到什么地方,都不完全是ostream。它很可能是从ostream中派生出来的。在这种情况下,template T将是更好的匹配。

编辑:

撇开风格评论不谈(它们是对的,你最好听听),这里有一个解决方案:

#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;
}