<< 运算符覆盖使用 g++ 编译而不是 windows

<< operator override compiles with g++ not windows

我正在尝试将应用程序移植到 windows(讽刺的是,我知道)。下面的简单例子说明了这个问题。使用 VS12 和 VS14 编译时出现以下错误:

C2679 binary '<<': no operator found which takes a right-hand operand
of type 'std::chrono::time_point<std::chrono::system_clock,std::chrono::system_clock::duration>'
(or there is no acceptable conversion)

使用 g++ 在 Ubuntu 上没有错误。我错过了什么?

logger.h

#pragma once
#include "stdafx.h"

#include <chrono>
#include <ostream>

std::ostream& operator<<(std::ostream &out, const std::chrono::time_point<std::chrono::system_clock> &time_point);

namespace Logging
{
    inline std::chrono::time_point<std::chrono::system_clock> timestamp()
    {
        std::chrono::time_point<std::chrono::system_clock> time_point = std::chrono::system_clock::now();
        return time_point;
    }

    class Event
    {
    public:
        Event() : time_point(timestamp())
        {
        }
        typedef std::unique_ptr< Event > Ptr;

        friend std::ostream& operator<<(std::ostream &out, const Ptr &p)
        {
            out << p->time_point << "\t";  // << LINE CAUSING ERROR
            return out;
        }

    private:
        const std::chrono::time_point<std::chrono::system_clock> time_point;
    };
}

logger.cpp

#include "stdafx.h"

#include <memory>
#include <ctime>
#include "logger.h"


std::ostream& operator<<(std::ostream &out, const std::chrono::time_point<std::chrono::system_clock> &time_point)
{
    std::time_t time = std::chrono::system_clock::to_time_t(time_point);
    struct tm t;
    localtime_s(&t, &time);  //localtime(&time) on linux
    char buf[30];
    int ret = ::strftime(buf, 30, "%Y/%m/%d %T", &t);
    out << buf;
    return out;
}

命令和标志

linux:

g++ -std=c++11 -Wall logger.cpp app.cpp -o app

windows:

/Yu"stdafx.h" /GS /analyze- /W3 /Zc:wchar_t /ZI /Gm /Od /sdl /Fd"Debug\vc140.pdb" /Zc:inline /fp:precise /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /Oy- /MDd /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\chron_test.pch" 

简答:这是 MSVC 2015 及更早版本中的编译器错误;要解决它,请在给出错误的行之前写 using ::operator<<;


此问题涉及内联好友定义的名称查找。 这是问题的简化示例:

namespace R { struct S {}; }
void f(R::S) {}

namespace N
{
    struct E
    {
        R::S var;
        friend void f(E e) { f(e.var); }   // OK, should find f(R::S)
    };
}

一般来说,非限定名称查找会做两件事:

  • 在当前范围内查找名称。如果未找到,请查看父范围等,直至并包括全局命名空间。 找到名字后停止。也就是说,如果该名称在当前作用域中并且也在父作用域中,则不会找到父作用域中的名称。
  • ADL,即也搜索函数调用的任何参数的命名空间。

请注意,在此代码中 f(R::S) 未在命名空间 R 中声明,因此 ADL 永远找不到它。它只能通过不合格查找的第一部分找到。

因此存在一个潜在的问题,即 namespace N 中出现的任何名称 f 都可能隐藏全局 f。如果您删除 friend 行并将 void f(E e) { f(e.var); } 作为函数放在 N 中(而不是 E 中),您可以看到它的实际效果。然后名称查找找到 N::f,并停止搜索,再也找不到 ::f

现在,在 class 中查找 friend 函数 首次定义 的名称有点不寻常。引用自 cppreference:

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided.

这意味着在调用 f(e.var) 中,函数 N::f 实际上对于查找是不可见的。所以搜索应该继续向上范围,直到我们找到 ::f,并成功。

MSVC 2015 似乎确实知道这个好友查找规则,因为它正确地拒绝了 struct A { friend void a() { a(); } }; ,但是它随后无法继续查找外部范围以获取另一个名称声明。

using ::operator<<;声明表示搜索N会找到::operator<<;看到 MSVC 2015 接受了这一点,显然它仍在搜索 N 但如果搜索失败则不会向上递归。


评论: 当你有 ADL 找不到的 operator<< 时,这个函数名称被隐藏的问题总是一个问题。即使在正确的编译器中,当遇到一些不相关的 operator<< 干扰时,您可能会发现您的代码恼人地停止工作。例如,如果您在 namespace Logging 中定义一个 operator<< 那么,即使在 g++ 和 MSVC 2017 中,代码也会停止工作。

相同的 using ::operator<< 变通方法在这种情况下有效,但不得不继续这样做很烦人。你真的必须在声明自己的任何类型的 operator<< 的任何命名空间 N 中执行 using ::operator<<,这仍然很烦人,但不那么烦人了。为此目的,最好使用具有某种独特名称的函数,而不是 operator<<

请注意,ADL 无法找到 operator<<(std::ostream, std::chrono...),因为所有参数都在 std 中,但 ::operator<< 不在 std 中。将您自己的自由函数添加到 namespace std 是未定义的行为,因此您也不能那样解决它。