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

时间:2009-05-01 14:53:16

标签: c++ visual-c++ operator-overloading temporary

为什么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

5 个答案:

答案 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 ++语言规则引起的:

  1. 通过调用构造函数创建的临时值是 rvalue
  2. rvalue可能不会绑定到非const引用。
  3. 但是,rvalue对象可以在其上调用非const方法。
  4. 正在发生的事情是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是数组的长度。