<< 运算符覆盖使用 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
是未定义的行为,因此您也不能那样解决它。
我正在尝试将应用程序移植到 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
是未定义的行为,因此您也不能那样解决它。