代码之家  ›  专栏  ›  技术社区  ›  Carlos A. Ibarra

为什么VisualC++编译器在这里调用错误的超载?

  •  3
  • Carlos A. Ibarra  · 技术社区  · 15 年前

    为什么VisualC++编译器在这里调用错误的超载?

    我有一个子类ostream,我用它来定义格式化缓冲区。有时,我希望创建一个临时的,然后使用通常的<<运算符立即向其中插入一个字符串,如下所示:

    M2Stream() << "the string";
    

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

    我将下面的示例编写为一个测试,在该测试中,我定义了自己的M2stream类,以重现问题。

    我认为问题在于m2stream()表达式产生了一个临时的,这在某种程度上导致编译器更喜欢void*重载。但为什么呢?事实证明,如果我为非成员重载const m2stream&做第一个参数,就会得到一个歧义。

    另一个奇怪的事情是,如果我先定义一个const char*类型的变量,然后调用它,而不是一个文本char字符串,它会调用所需的const char*重载,如下所示:

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

    就好像文本字符串的类型不同于常量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 回复  |  直到 12 年前
        1
  •  10
  •   Johannes Schaub - litb    15 年前

    Stream() << "hello"; operator<< char const*

    . 字符串文字是常量字符的数组。但我想这对你来说没关系。我不知道什么超载 操作员<< 添加了MSVC++。允许添加更多的重载,只要它们不影响有效程序的行为。

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

    现在,因为有一个成员运算符 void const* 和A 字符数 可以转换为该值,将选择该运算符并输出地址,因为 空穴常数 过载是为了。

    我在你的代码中看到你实际上有一个 void* 过载,而不是 空穴常数 超载。好吧,字符串文字可以转换为 char* ,即使字符串文本的类型是 char const[N] (n是您输入的字符数)。但这种转换已被否决。字符串文本转换为 空洞* . 在我看来,这是MSVC++编译器的另一个扩展。但这也解释了为什么字符串文本的处理方式与 字符数 指针。标准规定如下:

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

        2
  •  5
  •   j_random_hacker    15 年前

    第一个问题是由奇怪和狡猾的C++语言规则引起的:

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

    发生什么事了? ostream& operator<<(ostream&, const char*) 非成员函数,尝试绑定 M2Stream 临时创建到非常量引用,但失败(规则2);但是 ostream& ostream::operator<<(void*) 是一个成员函数,因此可以绑定到它。在没有 const char* 功能,选择最佳过载。

    我不知道为什么iostreams库的设计者决定 operator<<() 对于 void* 一种方法,但不是 运算符<<() 对于 常量字符 但事实就是这样,所以我们要处理这些奇怪的矛盾。

    我不知道为什么会出现第二个问题。您在不同的编译器中得到相同的行为吗?它可能是编译器或C++标准库Bug,但我会把它作为最后的借口——至少看看你是否能用正则表达式复制行为。 ostream 第一。

        3
  •  1
  •   Richard Corden    15 年前

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

    M2Stream ms;
    ms << "the string";
    

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

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

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

        4
  •  0
  •   anon    15 年前

    我不确定您的代码是否应该编译。我想:

    M2Stream & operator<<( void *vp )
    

    应该是:

    M2Stream & operator<<( const void *vp )
    

    事实上,更多地看代码,我相信您所有的问题都归结为常量。以下代码按预期工作:

    #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;
    }
    
        5
  •  0
  •   unwesen    15 年前

    您可以使用像下面这样的重载:

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

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