为什么 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 ++语言规则引起的:

  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* > >而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是数组的长度。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top