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

发布于 2024-07-17 19:03:48 字数 2928 浏览 9 评论 0原文

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

Why is the Visual C++ compiler calling the wrong overload here?

I am have a subclass of ostream that I use to define a buffer for formatting. Sometimes I want to create a temporary and immediately insert a string into it with the usual << operator like this:

M2Stream() << "the string";

Unfortunately, the program calls the operator<<(ostream, void *) member overload, instead of the operator<<(ostream, const char *) nonmember one.

I wrote the sample below as a test where I define my own M2Stream class that reproduces the problem.

I think the problem is that the M2Stream() expression produces a temporary and this somehow causes the compiler to prefer the void * overload. But why? This is borne out by the fact that if I make the first argument for the nonmember overload const M2Stream &, I get an ambiguity.

Another strange thing is that it calls the desired const char * overload if I first define a variable of type const char * and then call it, instead of a literal char string, like this:

const char *s = "char string variable";
M2Stream() << s;  

It's as if the literal string has a different type than the const char * variable! Shouldn't they be the same? And why does the compiler cause a call to the void * overload when I use the temporary and the literal char string?

#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;
}

Output:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

绮烟 2024-07-24 19:03:48

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

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

那么,是的,字符串文字的类型与 char const* 不同。 字符串文字是常量字符的数组。 但我认为这对你来说并不重要。 我不知道 operator<< MSVC++ 添加了哪些重载。 允许添加更多重载,只要它们不影响有效程序的行为。

为什么M2Stream() << s; 即使第一个参数是非常量引用也能工作...好吧,MSVC++ 有一个扩展,允许非常量引用绑定到临时变量。 将警告级别设置为 4 级以查看相关警告(例如“使用了非标准扩展...”)。

现在,因为有一个成员运算符<< 需要一个 void const*,并且 char const* 可以转换为该值,将选择该运算符,并且将输出地址,因为这就是 void const* 重载是为了。

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

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

The compiler is doing the right thing: Stream() << "hello"; should use the operator<< defined as a member function. Because the temporary stream object cannot be bound to a non-const reference but only to a const reference, the non-member operator that handles char const* won't be selected.

And it's designed that way, as you see when you change that operator. You get ambiguities, because the compiler can't decide which of the available operators to use. Because all of them were designed with rejection of the non-member operator<< in mind for temporaries.

Then, yes, a string literal has a different type than a char const*. A string literal is an array of const characters. But that wouldn't matter in your case, i think. I don't know what overloads of operator<< MSVC++ adds. It's allowed to add further overloads, as long as they don't affect the behavior of valid programs.

For why M2Stream() << s; works even when the first parameter is a non-const reference... Well, MSVC++ has an extension that allows non-const references bind to temporaries. Put the warning level on level 4 to see a warning of it about that (something like "non-standard extension used...").

Now, because there is a member operator<< that takes a void const*, and a char const* can convert to that, that operator will be chosen and the address will be output as that's what the void const* overload is for.

I've seen in your code that you actually have a void* overload, not a void const* overload. Well, a string literal can convert to char*, even though the type of a string literal is char const[N] (with N being the amount of characters you put). But that conversion is deprecated. It should be not standard that a string literal converts to void*. It looks to me that is another extension by the MSVC++ compiler. But that would explain why the string literal is treated differently than the char const* pointer. This is what the Standard says:

A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type "pointer to char"; a wide string literal can be converted to an rvalue of type "pointer to wchar_t". In either case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex D. ]

山田美奈子 2024-07-24 19:03:48

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

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

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

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

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

The first problem is caused by weird and tricky C++ language rules:

  1. A temporary created by a call to a constructor is an rvalue.
  2. An rvalue may not be bound to a non-const reference.
  3. However, an rvalue object can have non-const methods invoked on it.

What is happening is that ostream& operator<<(ostream&, const char*), a non-member function, attempts to bind the M2Stream temporary you create to a non-const reference, but that fails (rule #2); but ostream& ostream::operator<<(void*) is a member function and therefore can bind to it. In the absence of the const char* function, it is selected as the best overload.

I'm not sure why the designers of the IOStreams library decided to make operator<<() for void* a method but not operator<<() for const char*, but that's how it is, so we have these weird inconsistencies to deal with.

I'm not sure why the second problem is occurring. Do you get the same behaviour across different compilers? It's possible that it's a compiler or C++ Standard Library bug, but I'd leave that as the excuse of last resort -- at least see if you can replicate the behaviour with a regular ostream first.

流心雨 2024-07-24 19:03:48

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

M2Stream ms;
ms << "the string";

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

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

我相信 8.5.3/5 是涵盖此内容的标准部分。

The problem is that you're using a temporary stream object. Change the code to the following and it will work:

M2Stream ms;
ms << "the string";

Basically, the compiler is refusing to bind the temporary to the non const reference.

Regarding your second point about why it binds when you have a "const char *" object, this I believe is a bug in the VC compiler. I cannot say for certain, however, when you have just the string literal, there is a conversion to 'void *' and a conversion to 'const char *'. When you have the 'const char *' object, then there is no conversion required on the second argument - and this might be a trigger for the non-standard behaviour of VC to allow the non const ref bind.

I believe 8.5.3/5 is the section of the standard that covers this.

深巷少女 2024-07-24 19:03:48

我不确定你的代码是否应该编译。 我认为:

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;
}

I'm not sure that your code should compile. I think:

M2Stream & operator<<( void *vp )

should be:

M2Stream & operator<<( const void *vp )

In fact, looking at the code more, I believe all your problems are down to const. The following code works as expected:

#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;
}
野味少女 2024-07-24 19:03:48

您可以使用如下重载:

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

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

You could use an overload such as this one:

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

As an added bonus, you now know N to be the length of the array.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文