簡體   English   中英

為什么Visual C ++編譯器在這里調用錯誤的重載?

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

為什么Visual C ++編譯器在這里調用錯誤的重載?

我有一個ostream的子類,我用它來定義格式化的緩沖區。 有時我想創建一個臨時的,並立即插入一個字符串,使用通常的<<運算符,如下所示:

M2Stream() << "the string";

不幸的是,程序調用運算符<<(ostream,void *)成員重載,而不是運算符<<(ostream,const char *)非成員。

我將下面的示例編寫為測試,我在其中定義了自己的M2Stream類來重現問題。

我認為問題是M2Stream()表達式產生一個臨時的,這在某種程度上導致編譯器更喜歡void * overload。 但為什么? 事實證明,如果我為非成員重載const M2Stream&做第一個參數,我就會產生歧義。

另一個奇怪的事情是,如果我首先定義一個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

編譯器正在做正確的事: Stream() << "hello"; 應該使用operator<<定義為成員函數。 由於臨時流對象不能綁定到非const引用,而只能綁定到const引用,因此不會選擇處理char const*的非成員運算符。

正如您所知,當您更改該運算符時,它就是這樣設計的。 您會產生歧義,因為編譯器無法決定使用哪些可用的運算符。 因為所有這些都被設計成拒絕非成員operator<<記住臨時工。

然后,是的,字符串文字具有與char const*不同的類型。 字符串文字是一個const字符數組。 但是,我想,這對你的情況無關緊要。 我不知道operator<< MSVC ++的重載是什么。 只要它們不影響有效程序的行為,就允許添加進一步的重載。

為什么M2Stream() << s; 即使第一個參數是非const引用也可以工作......好吧,MSVC ++有一個擴展,允許非const引用綁定到temporaries。 將警告級別置於級別4以查看有關該級別的警告(類似“使用的非標准擴展......”)。

現在,因為有一個成員運算符<<,它接受一個void const* ,一個char const*可以轉換為那個,所以將選擇該運算符,並輸出地址,就像void const* overload所用的那樣。

我在你的代碼中看到你實際上有一個void*重載,而不是一個void const*重載。 好吧,字符串文字可以轉換為char* ,即使字符串文字的類型是char const[N] (N是你放置的字符數)。 但該轉換已被棄用。 字符串文字轉換為void*應該不是標准的。 在我看來,這是MSVC ++編譯器的另一個擴展。 但這可以解釋為什么字符串文字的處理方式與char const*指針不同。 這就是標准所說的:

不是寬字符串文字的字符串文字(2.13.4)可以轉換為“指向char的指針”的右值; 可以將寬字符串文字轉換為“指向wchar_t的指針”類型的右值。 在任何一種情況下,結果都是指向數組第一個元素的指針。 僅當存在明確的適當指針目標類型時才考慮此轉換,而不是在通常需要從左值轉換為右值時。 [注意:此轉換已棄用。 見附件D.]

第一個問題是由奇怪而棘手的C ++語言規則引起的:

  1. 通過調用構造函數創建的臨時右值
  2. 右值可能不會綁定到非const引用。
  3. 但是,rvalue對象可以在其上調用非const方法。

發生的事情是ostream& operator<<(ostream&, const char*) ,一個非成員函數,試圖將你創建的M2Stream臨時綁定到非const引用,但是失敗了(規則#2); 但是ostream& ostream::operator<<(void*)是一個成員函數,因此可以綁定到它。 在沒有const char*函數的情況下,它被選為最佳過載。

我不知道為什么Iostreams庫的設計者決定讓operator<<()void*一個方法,但不是operator<<()用於const char* ,但是這是怎么回事,所以我們有這些奇怪的不一致之處處理。

我不確定為什么會出現第二個問題。 您是否在不同的編譯器中獲得相同的行為? 它可能是編譯器或C ++標准庫的錯誤,但我會把它作為最后的借口 - 至少看看你是否可以先用常規的ostream復制行為。

問題是您正在使用臨時流對象。 將代碼更改為以下內容,它將起作用:

M2Stream ms;
ms << "the string";

基本上,編譯器拒絕將臨時綁定到非const引用。

關於你有一個“const char *”對象時為什么會綁定的第二點,我相信這是VC編譯器中的一個錯誤。 我不能肯定地說,當你只有字符串文字時,轉換為'void *'並轉換為'const char *'。 如果你有'const char *'對象,那么第二個參數就不需要轉換 - 這可能是VC的非標准行為的觸發器,允許非const ref綁定。

我相信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 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM