为什么Visual C ++编译器在这里调用了错误的重载?
我有一个ostream的子类,我用它来定义格式化的缓冲区。有时我想创建一个临时的,并立即插入一个字符串,通常<<像这样的运算符:
M2Stream() << "the string";
不幸的是,程序调用运算符&lt;&lt;(ostream,void *)成员重载,而不是运算符&lt;&lt;(ostream,const char *)非成员1。
我将下面的示例编写为测试,我在其中定义了自己的M2Stream类来重现问题。
我认为问题是M2Stream()表达式产生一个临时的,这会导致编译器更喜欢void * overload。但为什么?事实证明,如果我为非成员过载const M2Stream&amp;做出第一个参数,我就会产生歧义。
另一个奇怪的事情是,如果我首先定义一个const char *类型的变量然后调用它而不是文字字符串,它调用所需的const 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
答案 0 :(得分:10)
编译器正在做正确的事情:Stream() << "hello";
应该使用定义为成员函数的operator<<
。由于临时流对象不能绑定到非const引用,而只能绑定到const引用,因此不会选择处理char const*
的非成员运算符。
它就是这样设计的,就像你改变那个操作符时所看到的那样。您会产生歧义,因为编译器无法决定使用哪些可用的运算符。因为所有这些都被设计为对临时成员拒绝非成员operator<<
。
然后,是的,字符串文字的类型与char const*
不同。字符串文字是一个const字符数组。但是,我想,这对你的情况无关紧要。我不知道operator<<
MSVC ++的重载是什么。只要它们不影响有效程序的行为,就允许添加进一步的重载。
为什么M2Stream() << s;
即使在第一个参数是非const引用时也能正常工作......好吧,MSVC ++有一个扩展,允许非const引用绑定到temporaries。将警告级别置于级别4以查看有关该级别的警告(类似“使用的非标准扩展名......”)。
现在,因为有一个成员运营商&lt;&lt;需要void const*
,而char const*
可以转换为该选项,将选择该运算符,并输出地址作为void const*
重载的内容。
我在您的代码中看到您确实有void*
重载,而不是void const*
重载。好吧,字符串文字可以转换为char*
,即使字符串文字的类型是char const[N]
(N是您放置的字符数)。但该转换已被弃用。字符串文字转换为void*
不应该是标准的。在我看来,这是MSVC ++编译器的另一个扩展。但这可以解释为什么字符串文字的处理方式与char const*
指针不同。这就是标准所说的:
不是宽字符串文字的字符串文字(2.13.4)可以转换为“指向字符的指针”的右值;可以将宽字符串文字转换为“指向wchar_t的指针”类型的右值。在任何一种情况下,结果都是指向数组第一个元素的指针。仅当存在明确的适当指针目标类型时才考虑此转换,而不是在通常需要从左值转换为右值时。 [注意:此转换已弃用。见附件D.]
答案 1 :(得分:5)
第一个问题是由奇怪而棘手的C ++语言规则引起的:
正在发生的事情是ostream& operator<<(ostream&, const char*)
,一个非成员函数,试图将您创建的M2Stream
临时值绑定到非const引用,但是失败(规则#2);但ostream& ostream::operator<<(void*)
是一个成员函数,因此可以绑定它。如果没有const char*
函数,则会将其选为最佳过载。
我不确定为什么IOStreams库的设计师决定operator<<()
为void*
方法而不是operator<<()
为const char*
,但这就是它的方式,所以我们要处理这些奇怪的不一致。
我不确定为什么会出现第二个问题。您是否在不同的编译器中获得相同的行为?它可能是编译器或C ++标准库的错误,但我会将其作为最后的借口 - 至少看看你是否可以首先使用常规ostream
复制行为。
答案 2 :(得分:1)
问题是您正在使用临时流对象。将代码更改为以下内容,它将起作用:
M2Stream ms;
ms << "the string";
基本上,编译器拒绝将临时绑定到非const引用。
关于为什么当你有一个“const char *”对象时它绑定的第二点,我相信这是VC编译器中的一个错误。我不能肯定地说,当你只有字符串文字时,转换为'void *'并转换为'const char *'。如果你有'const char *'对象,那么第二个参数就不需要转换 - 这可能是VC的非标准行为的触发器,允许非const ref绑定。
我相信8.5.3 / 5是涵盖此内容的标准部分。
答案 3 :(得分:0)
我不确定您的代码是否应该编译。我想:
M2Stream & operator<<( void *vp )
应该是:
M2Stream & operator<<( const void *vp )
事实上,更多地查看代码,我相信你所有的问题都归结为const。以下代码按预期工作:
#include <iostream>
using namespace std;
class M2Stream
{
};
const M2Stream & operator<<( const M2Stream &os, const char *val)
{
cout << "M2Stream good operator<<(const char *) called with " << val << endl;
return os;
}
int main(int argc, char argv[])
{
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;
}
答案 4 :(得分:0)
你可以使用像这样的重载:
template <int N>
M2Stream & operator<<(M2Stream & m, char const (& param)[N])
{
// output param
return m;
}
作为额外的奖励,你现在知道N是数组的长度。