3

Visual C++ コンパイラがここで間違ったオーバーロードを呼び出すのはなぜですか?

フォーマット用のバッファを定義するために使用する ostream のサブクラスがあります。一時的なものを作成し、次のような通常の << 演算子を使用してすぐに文字列を挿入したい場合があります。

M2Stream() << "the string";

残念ながら、このプログラムは、operator<<(ostream, const char *) 非メンバー オーバーロードではなく、operator<<(ostream, void *) メンバー オーバーロードを呼び出します。

問題を再現する独自の M2Stream クラスを定義するテストとして、以下のサンプルを作成しました。

問題は、 M2Stream() 式が一時的なものを生成し、これにより何らかの形でコンパイラが void * オーバーロードを優先することだと思います。しかし、なぜ?これは、非メンバー オーバーロード const M2Stream & の最初の引数を作成すると、あいまいさが生じるという事実によって裏付けられています。

もう 1 つの奇妙な点は、次のように、リテラル char 文字列の代わりに、最初に const char * 型の変数を定義してから呼び出すと、目的の const char * オーバーロードが呼び出されることです。

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

リテラル文字列が const char * 変数とは異なる型を持っているかのようです! それらは同じであるべきではありませんか?また、一時文字列とリテラル 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
4

5 に答える 5

10

コンパイラは正しいことをしています:定義済みをメンバー関数としてStream() << "hello";使用する必要があります。operator<<一時ストリーム オブジェクトは非 const 参照にバインドできず、const 参照にのみバインドできるため、処理char const*する非メンバー オペレーターは選択されません。

そして、その演算子を変更するとわかるように、そのように設計されています。コンパイラは使用可能な演算子のどれを使用するかを決定できないため、あいまいさが生じます。いずれも一時的な非会員の排除をoperator<<念頭に置いて設計されているためです。

次に、はい、文字列リテラルは a とは異なる型を持ちchar const*ます。文字列リテラルは const 文字の配列です。しかし、それはあなたの場合は問題ではないと思います。operator<<MSVC++ のどのオーバーロードが追加されるのかわかりません。有効なプログラムの動作に影響を与えない限り、さらにオーバーロードを追加することができます。

最初のパラメーターが非 const 参照であっても機能するのはなぜM2Stream() << s;ですか... MSVC++ には、非 const 参照を一時変数にバインドできる拡張機能があります。警告レベルをレベル 4 にすると、それに関する警告が表示されます (「非標準の拡張機能が使用されています...」など)。

ここで、 を受け取るメンバー operator<< がありvoid const*、 achar const*がそれに変換できるため、その演算子が選択され、それがvoid const*オーバーロードの目的であるため、アドレスが出力されます。

あなたのコードで、実際にはvoid*オーバーロードではなくオーバーロードがあることがvoid const*わかりました。char*文字列リテラルの型が であっても、文字列リテラルは に変換できますchar const[N](N は入力した文字数です)。しかし、その変換は非推奨です。文字列リテラルが に変換されるのは標準的であってはなりませんvoid*。これは、MSVC++ コンパイラによる別の拡張機能のように思えます。char const*しかし、文字列リテラルがポインターとは異なる方法で扱われる理由は、これで説明できます。これは、標準が言うことです:

ワイド文字列リテラルではない文字列リテラル (2.13.4) は、「char へのポインタ」型の右辺値に変換できます。ワイド文字列リテラルは、「wchar_t へのポインター」型の右辺値に変換できます。いずれの場合も、結果は配列の最初の要素へのポインターです。この変換は、明示的な適切なポインター ターゲット型がある場合にのみ考慮され、左辺値から右辺値に変換する必要がある場合は考慮されません。[注: この変換は非推奨です。附属書 D を参照。 ]

于 2009-05-01T15:09:48.583 に答える
5

最初の問題は、奇妙でトリッキーな C++ 言語の規則によって引き起こされます。

  1. コンストラクターの呼び出しによって作成される一時はrvalueです。
  2. 右辺値は非 const 参照にバインドできません。
  3. ただし、右辺値オブジェクトでは、const 以外のメソッドを呼び出すことができます。

何が起こっているかというostream& operator<<(ostream&, const char*)と、非メンバー関数は、M2Stream作成した一時を非 const 参照にバインドしようとしますが、失敗します (ルール #2)。しかしostream& ostream::operator<<(void*)、メンバー関数であるため、それにバインドできます。const char*関数がない場合は、最適なオーバーロードとして選択されます。

IOStreams ライブラリの設計者がメソッドではなくメソッドを作成することを決定した理由はわかりませんoperator<<()が、そのため、これらの奇妙な矛盾に対処する必要があります。void*operator<<()const char*

2番目の問題が発生している理由がわかりません。異なるコンパイラ間で同じ動作が得られますか? コンパイラまたは C++ 標準ライブラリのバグである可能性がありますが、それは最後の手段として残しておきます。少なくとも、ostream最初に通常の動作を再現できるかどうかを確認してください。

于 2009-05-01T15:12:46.050 に答える
1

問題は、一時的なストリームオブジェクトを使用していることです。コードを次のように変更すると、機能します。

M2Stream ms;
ms << "the string";

基本的に、コンパイラは一時的なものを非const参照にバインドすることを拒否しています。

「constchar*」オブジェクトがあるときにバインドする理由についての2番目のポイントに関しては、これはVCコンパイラのバグだと思います。確かなことは言えませんが、文字列リテラルだけの場合は、「void*」への変換と「constchar*」への変換があります。'const char *'オブジェクトがある場合、2番目の引数で変換は必要ありません-これは、非constrefバインドを許可するVCの非標準動作のトリガーである可能性があります。

8.5.3/5はこれをカバーする規格のセクションだと思います。

于 2009-05-01T15:04:19.783 に答える
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;
}
于 2009-05-01T15:03:29.830 に答える
0

次のようなオーバーロードを使用できます。

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

おまけとして、N が配列の長さであることがわかりました。

于 2009-05-01T16:16:06.413 に答える