3

为什么 Visual C++ 编译器在这里调用错误的重载?

我有一个 ostream 的子类,用于定义格式化缓冲区。有时我想创建一个临时的并立即使用通常的 << 运算符将一个字符串插入其中,如下所示:

M2Stream() << "the string";

不幸的是,程序调用 operator<<(ostream, void *) 成员重载,而不是 operator<<(ostream, const char *) 非成员重载。

我编写了下面的示例作为测试,我在其中定义了自己的 M2Stream 类来重现问题。

我认为问题在于 M2Stream() 表达式会产生一个临时的,这会导致编译器更喜欢 void * 重载。但为什么?事实证明,如果我为非成员重载 const M2Stream & 设置第一个参数,我会感到模棱两可。

另一个奇怪的事情是,如果我首先定义一个 const char * 类型的变量然后调用它,而不是文字 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
4

5 回答 5

10

编译器正在做正确的事情:Stream() << "hello";应该使用operator<<定义的成员函数。因为临时流对象不能绑定到非 const 引用而只能绑定到 const 引用,所以char const*不会选择处理的非成员运算符。

正如您在更改该运算符时所看到的那样,它就是这样设计的。您会产生歧义,因为编译器无法决定使用哪些可用的运算符。operator<<因为所有这些都是在设计时考虑到临时拒绝非会员的。

那么,是的,字符串文字的类型与 a 不同char const*。字符串文字是 const 字符的数组。但这在你的情况下并不重要,我认为。我不知道operator<<MSVC++ 的重载增加了什么。允许添加更多的重载,只要它们不影响有效程序的行为。

为什么M2Stream() << s;即使第一个参数是非常量引用也能工作...嗯,MSVC++ 有一个扩展,允许非常量引用绑定到临时对象。将警告级别设置为级别 4 以查看有关它的警告(类似于“使用非标准扩展...”)。

现在,因为有一个成员 operator<< 接受 a void const*,并且 achar const*可以转换为它,所以将选择该运算符并输出地址,因为这就是void const*重载的目的。

我在您的代码中看到您实际上有void*重载,而不是void const*重载。好吧,字符串文字可以转换为char*,即使字符串文字的类型是char const[N](其中 N 是您输入的字符数)。但是这种转换已被弃用。字符串文字转换为void*. 在我看来,这是 MSVC++ 编译器的另一个扩展。但这可以解释为什么字符串文字的处理方式与char const*指针不同。这就是标准所说的:

不是宽字符串文字的字符串文字(2.13.4)可以转换为“pointer to char”类型的右值;宽字符串文字可以转换为“指向 wchar_t 的指针”类型的右值。无论哪种情况,结果都是指向数组第一个元素的指针。仅当存在显式适当的指针目标类型时才考虑这种转换,而不是当一般需要从左值转换为右值时。[注意:此转换已弃用。见附件 D。]

于 2009-05-01T15:09:48.583 回答
5

第一个问题是由怪异而棘手的 C++ 语言规则引起的:

  1. 通过调用构造函数创建的临时值是右值
  2. 右值不能绑定到非常量引用。
  3. 但是,右值对象可以调用非常量方法。

正在发生的事情是ostream& operator<<(ostream&, const char*),一个非成员函数,尝试将M2Stream您创建的临时对象绑定到非常量引用,但失败了(规则 #2);butostream& ostream::operator<<(void*)是一个成员函数,因此可以绑定到它。在没有该const char*功能的情况下,它被选为最佳重载。

我不确定为什么 IOStreams 库的设计者决定operator<<()void*一个方法而不是operator<<()const char*,但事实就是这样,所以我们需要处理这些奇怪的不一致。

我不确定为什么会出现第二个问题。您是否在不同的编译器中获得相同的行为?它可能是编译器或 C++ 标准库错误,但我将把它作为最后手段的借口——至少看看你是否可以ostream先用常规方法复制该行为。

于 2009-05-01T15:12:46.050 回答
1

问题是您使用的是临时流对象。将代码更改为以下内容,它将起作用:

M2Stream ms;
ms << "the string";

基本上,编译器拒绝将临时绑定到非 const 引用。

关于第二点关于为什么当你有一个“const char *”对象时它会绑定,我认为这是 VC 编译器中的一个错误。但是,我不能肯定地说,当您只有字符串文字时,会转换为“void *”并转换为“const char *”。当您拥有“const char *”对象时,第二个参数不需要转换 - 这可能是 VC 允许非 const ref 绑定的非标准行为的触发器。

我相信 8.5.3/5 是涵盖这一点的标准部分。

于 2009-05-01T15:04:19.783 回答
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;
}
于 2009-05-01T15:03:29.830 回答
0

您可以使用这样的重载:

template <int N>
M2Stream & operator<<(M2Stream & m, char const (& param)[N])
{
     // output param
     return m;
}

作为额外的奖励,您现在知道 N 是数组的长度。

于 2009-05-01T16:16:06.413 回答