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