为什么 Visual C++ 编译器在这里调用了错误的重载?
为什么 Visual C++ 编译器在这里调用错误的重载?
我有一个 ostream 的子类,用于定义格式化缓冲区。 有时我想创建一个临时文件并立即使用通常的 << 向其中插入一个字符串。 像这样的运算符:
M2Stream() << "the string";
不幸的是,程序调用了operator<<(ostream, void *)成员重载,而不是operator<<(ostream, const char *)非成员重载。
我编写了下面的示例作为测试,其中我定义了自己的 M2Stream 类来重现问题。
我认为问题在于 M2Stream() 表达式产生一个临时值,这在某种程度上导致编译器更喜欢 void * 重载。 但为什么? 事实证明,如果我为非成员重载 const M2Stream & 提供第一个参数,则会出现歧义。
另一个奇怪的事情是,如果我首先定义一个 const char * 类型的变量然后调用它,它会调用所需的 const char * 重载,而不是文字 char 字符串,如下所示
const char *s = "char string variable";
M2Stream() << s;
: const char * 变量! 它们不应该是一样的吗? 当我使用临时字符串和文字字符串时,为什么编译器会导致调用 void * 重载?
#include "stdafx.h"
#include <iostream>
using namespace std;
class M2Stream
{
public:
M2Stream &operator<<(void *vp)
{
cout << "M2Stream bad operator<<(void *) called with " << (const char *) vp << endl;
return *this;
}
};
/* If I make first arg const M2Stream &os, I get
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(39) : error C2666: 'M2Stream::operator <<' : 2 overloads have similar conversions
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(13): could be 'M2Stream &M2Stream::operator <<(void *)'
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(20): or 'const M2Stream &operator <<(const M2Stream &,const char *)'
while trying to match the argument list '(M2Stream, const char [45])'
note: qualification adjustment (const/volatile) may be causing the ambiguity
*/
const M2Stream & operator<<(M2Stream &os, const char *val)
{
cout << "M2Stream good operator<<(const char *) called with " << val << endl;
return os;
}
int main(int argc, char argv[])
{
// This line calls void * overload, outputs: M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream() << "literal char string on constructed temporary";
const char *s = "char string variable";
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
M2Stream() << s;
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
M2Stream m;
m << "literal char string on prebuilt object";
return 0;
}
输出:
M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream good operator<<(const char *) called with char string variable
M2Stream good operator<<(const char *) called with literal char string on prebuilt object
Why is the Visual C++ compiler calling the wrong overload here?
I am have a subclass of ostream that I use to define a buffer for formatting. Sometimes I want to create a temporary and immediately insert a string into it with the usual << operator like this:
M2Stream() << "the string";
Unfortunately, the program calls the operator<<(ostream, void *) member overload, instead of the operator<<(ostream, const char *) nonmember one.
I wrote the sample below as a test where I define my own M2Stream class that reproduces the problem.
I think the problem is that the M2Stream() expression produces a temporary and this somehow causes the compiler to prefer the void * overload. But why? This is borne out by the fact that if I make the first argument for the nonmember overload const M2Stream &, I get an ambiguity.
Another strange thing is that it calls the desired const char * overload if I first define a variable of type const char * and then call it, instead of a literal char string, like this:
const char *s = "char string variable";
M2Stream() << s;
It's as if the literal string has a different type than the const char * variable! Shouldn't they be the same? And why does the compiler cause a call to the void * overload when I use the temporary and the literal char string?
#include "stdafx.h"
#include <iostream>
using namespace std;
class M2Stream
{
public:
M2Stream &operator<<(void *vp)
{
cout << "M2Stream bad operator<<(void *) called with " << (const char *) vp << endl;
return *this;
}
};
/* If I make first arg const M2Stream &os, I get
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(39) : error C2666: 'M2Stream::operator <<' : 2 overloads have similar conversions
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(13): could be 'M2Stream &M2Stream::operator <<(void *)'
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(20): or 'const M2Stream &operator <<(const M2Stream &,const char *)'
while trying to match the argument list '(M2Stream, const char [45])'
note: qualification adjustment (const/volatile) may be causing the ambiguity
*/
const M2Stream & operator<<(M2Stream &os, const char *val)
{
cout << "M2Stream good operator<<(const char *) called with " << val << endl;
return os;
}
int main(int argc, char argv[])
{
// This line calls void * overload, outputs: M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream() << "literal char string on constructed temporary";
const char *s = "char string variable";
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
M2Stream() << s;
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
M2Stream m;
m << "literal char string on prebuilt object";
return 0;
}
Output:
M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream good operator<<(const char *) called with char string variable
M2Stream good operator<<(const char *) called with literal char string on prebuilt object
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
编译器正在做正确的事情:
Stream() << "hello";
应使用定义为成员函数的运算符<<
。 由于临时流对象不能绑定到非常量引用,而只能绑定到常量引用,因此不会选择处理 char const* 的非成员运算符。正如您在更改运算符时所看到的那样,它就是这样设计的。 您会遇到歧义,因为编译器无法决定使用哪个可用运算符。 因为所有这些在设计时都考虑到了临时人员拒绝非会员操作员<<的情况。
那么,是的,字符串文字的类型与 char const* 不同。 字符串文字是常量字符的数组。 但我认为这对你来说并不重要。 我不知道
operator<<
MSVC++ 添加了哪些重载。 允许添加更多重载,只要它们不影响有效程序的行为。为什么
M2Stream() << s;
即使第一个参数是非常量引用也能工作...好吧,MSVC++ 有一个扩展,允许非常量引用绑定到临时变量。 将警告级别设置为 4 级以查看相关警告(例如“使用了非标准扩展...”)。现在,因为有一个成员运算符<< 需要一个
void const*
,并且char const*
可以转换为该值,将选择该运算符,并且将输出地址,因为这就是void const*
重载是为了。我在您的代码中看到您实际上有一个
void*
重载,而不是void const*
重载。 嗯,字符串文字可以转换为char*
,即使字符串文字的类型是char const[N]
(N 是您输入的字符数) 。 但这种转换已被弃用。 字符串文字转换为void*
不应该是标准的。 在我看来,这是 MSVC++ 编译器的另一个扩展。 但这可以解释为什么字符串文字的处理方式与 char const* 指针不同。 标准是这样说的:The compiler is doing the right thing:
Stream() << "hello";
should use theoperator<<
defined as a member function. Because the temporary stream object cannot be bound to a non-const reference but only to a const reference, the non-member operator that handleschar const*
won't be selected.And it's designed that way, as you see when you change that operator. You get ambiguities, because the compiler can't decide which of the available operators to use. Because all of them were designed with rejection of the non-member
operator<<
in mind for temporaries.Then, yes, a string literal has a different type than a
char const*
. A string literal is an array of const characters. But that wouldn't matter in your case, i think. I don't know what overloads ofoperator<<
MSVC++ adds. It's allowed to add further overloads, as long as they don't affect the behavior of valid programs.For why
M2Stream() << s;
works even when the first parameter is a non-const reference... Well, MSVC++ has an extension that allows non-const references bind to temporaries. Put the warning level on level 4 to see a warning of it about that (something like "non-standard extension used...").Now, because there is a member operator<< that takes a
void const*
, and achar const*
can convert to that, that operator will be chosen and the address will be output as that's what thevoid const*
overload is for.I've seen in your code that you actually have a
void*
overload, not avoid const*
overload. Well, a string literal can convert tochar*
, even though the type of a string literal ischar const[N]
(with N being the amount of characters you put). But that conversion is deprecated. It should be not standard that a string literal converts tovoid*
. It looks to me that is another extension by the MSVC++ compiler. But that would explain why the string literal is treated differently than thechar const*
pointer. This is what the Standard says:第一个问题是由奇怪且棘手的 C++ 语言规则引起的:
正在发生的事情是
ostream& 非成员函数运算符<<(ostream&, const char*)
尝试将您创建的M2Stream
临时对象绑定到非常量引用,但失败(规则#2); 但是ostream& ostream::operator<<(void*) 是一个成员函数,因此可以绑定到它。 在没有const char*
函数的情况下,它被选为最佳重载。我不确定为什么 IOStreams 库的设计者决定将
void*
的operator<<()
设为一个方法,而不是operator<<( )
为const char*
,但事实就是这样,所以我们需要处理这些奇怪的不一致问题。我不确定为什么会出现第二个问题。 您在不同的编译器中得到相同的行为吗? 这可能是编译器或 C++ 标准库错误,但我会将其作为最后手段的借口 - 至少看看是否可以首先使用常规
ostream
复制该行为。The first problem is caused by weird and tricky C++ language rules:
What is happening is that
ostream& operator<<(ostream&, const char*)
, a non-member function, attempts to bind theM2Stream
temporary you create to a non-const reference, but that fails (rule #2); butostream& ostream::operator<<(void*)
is a member function and therefore can bind to it. In the absence of theconst char*
function, it is selected as the best overload.I'm not sure why the designers of the IOStreams library decided to make
operator<<()
forvoid*
a method but notoperator<<()
forconst char*
, but that's how it is, so we have these weird inconsistencies to deal with.I'm not sure why the second problem is occurring. Do you get the same behaviour across different compilers? It's possible that it's a compiler or C++ Standard Library bug, but I'd leave that as the excuse of last resort -- at least see if you can replicate the behaviour with a regular
ostream
first.问题是您正在使用临时流对象。 将代码更改为以下内容,它将起作用:
基本上,编译器拒绝将临时值绑定到非常量引用。
关于你的第二点,即为什么当你有一个“const char *”对象时它会绑定,我认为这是 VC 编译器中的一个错误。 然而,我不能肯定地说,当你只有字符串文字时,就会有一个到“void *”的转换和一个到“const char *”的转换。 当您拥有“const char *”对象时,第二个参数不需要转换 - 这可能会触发 VC 的非标准行为,以允许非 const 引用绑定。
我相信 8.5.3/5 是涵盖此内容的标准部分。
The problem is that you're using a temporary stream object. Change the code to the following and it will work:
Basically, the compiler is refusing to bind the temporary to the non const reference.
Regarding your second point about why it binds when you have a "const char *" object, this I believe is a bug in the VC compiler. I cannot say for certain, however, when you have just the string literal, there is a conversion to 'void *' and a conversion to 'const char *'. When you have the 'const char *' object, then there is no conversion required on the second argument - and this might be a trigger for the non-standard behaviour of VC to allow the non const ref bind.
I believe 8.5.3/5 is the section of the standard that covers this.
我不确定你的代码是否应该编译。 我认为:
应该是:
其实多看代码,我相信你所有的问题都归结为const。 以下代码按预期工作:
I'm not sure that your code should compile. I think:
should be:
In fact, looking at the code more, I believe all your problems are down to const. The following code works as expected:
您可以使用如下重载:
作为额外的好处,您现在知道 N 是数组的长度。
You could use an overload such as this one:
As an added bonus, you now know N to be the length of the array.