我正在尝试将应用程序移植到win-dows(具有讽刺意味的是,我知道)。以下赤裸裸的例子说明了这个问题。使用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"
答案 0 :(得分:3)
简短回答:这是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)
};
}
通常,非限定名称查找会做两件事:
请注意,在此代码中f(R::S)
未在命名空间R
中声明,因此ADL永远找不到它。它只能通过不等式查找的第一部分找到。
因此,f
内发生的任何名称namespace N
都可能隐藏全局f
,这是一个潜在的问题。如果您移除friend
行并将void f(E e) { f(e.var); }
作为N
中的函数放入E
,则可以看到此操作。然后名称查找找到N::f
,并停止搜索,永远不会找到::f
。
现在,在类中首先定义<{1}}函数的名称查找有点不寻常。引自cppreference:
在类或类模板X中的友元声明中首先声明的名称成为X的最内层封闭命名空间的成员,但是对于查找是不可见的(除了与X相关的依赖于参数的查找),除非在提供了命名空间范围。
这意味着在调用friend
中,函数f(e.var)
实际上对查找不可见。因此,搜索应该继续增加范围,直到找到N::f
,然后成功。
MSVC 2015似乎确实知道这个朋友查找规则,因为它正确拒绝::f
,但是它无法继续查找外部范围以获取另一个名称声明。
struct A { friend void a() { a(); } };
声明表示搜索using ::operator<<;
会找到N
;看到MSVC 2015接受了这一点,显然它仍在搜索::operator<<
但是如果搜索失败则不会向上递归。
评论:如果ADL找不到N
,那么函数名称被遮蔽的问题始终是个问题。即使在正确的编译器中,当您遇到一些不相关的operator<<
干扰时,您可能会发现代码会烦恼地停止工作。例如,如果您在operator<<
中定义operator<<
,那么即使在g ++和MSVC 2017中,代码也会停止工作。
在这种情况下,相同的namespace Logging
解决方法可行,但是必须继续这样做很烦人。你真的必须在任何名称空间using ::operator<<
内部using ::operator<<
声明它自己的任何类型的N
,这仍然很烦人,但不那么令人讨厌。为此目的,最好使用具有某种独特名称的函数,而不是operator<<
。
请注意,ADL无法找到operator<<
,因为所有参数都在operator<<(std::ostream, std::chrono...)
中,但std
不在::operator<<
中。将您自己的免费函数添加到std
是未定义的行为,因此您无法以这种方式解决它。