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

StackOverflow https://stackoverflow.com/questions/811724

質問

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

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

M2Stream() << "the string";

残念ながら、プログラムはoperator <!> lt; <!> lt;(ostream、const char *)の代わりにoperator <!> lt; <!> lt;(ostream、void *)メンバーオーバーロードを呼び出します非会員。

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

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

別の奇妙なことは、const char *型の変数を最初に定義し、次にこのようにリテラル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*を処理する非メンバー演算子は選択されません。

そして、その演算子を変更するとわかるように、そのように設計されています。コンパイラは使用可能な演算子のどれを使用するかを決定できないため、あいまいさが生じます。それらはすべて、一時的なものとして非メンバーM2Stream() << s;を拒否するように設計されているためです。

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

最初のパラメーターが非const参照であってもchar*が機能する理由...まあ、MSVC ++には、非const参照が一時にバインドできる拡張機能があります。警告レベルをレベル4に設定して、それに関する警告を表示します(<!> quot; non-standard extension used ... <!> quot;など)。

今、メンバー演算子があるので<!> lt; <!> lt; char const[N]を受け取り、<=>がそれに変換できます。その演算子が選択され、アドレスが<=>オーバーロードの目的であるとして出力されます。

コードでは、実際には<=>オーバーロードではなく、<=>オーバーロードが発生していることがわかりました。さて、文字列リテラルのタイプは<=>(Nは入力する文字数)であっても、文字列リテラルは<=>に変換できます。しかし、その変換は非推奨です。文字列リテラルが<=>に変換されることは標準ではありません。それは、MSVC ++コンパイラによる別の拡張機能であるように見えます。しかし、文字列リテラルが<=>ポインターとは異なる方法で処理される理由は説明できます。これは、規格が言っていることです:

  

ワイド文字列リテラルではない文字列リテラル(2.13.4)は、型<!> quot; pointer to char <!> quot;の右辺値に変換できます。ワイド文字列リテラルは、タイプ<!> quot; wchar_t <!> quot;へのポインターの右辺値に変換できます。どちらの場合でも、結果は配列の最初の要素へのポインタです。この変換は、明示的に適切なポインターターゲットタイプがある場合にのみ考慮され、左辺値から右辺値に変換する一般的な必要性がある場合には考慮されません。 [注:この変換は非推奨です。付録Dを参照してください。]

他のヒント

最初の問題は、奇妙でトリッキーなC ++言語のルールが原因です。

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

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

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

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

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

M2Stream ms;
ms << "the string";

基本的に、コンパイラはテンポラリを非const参照にバインドすることを拒否しています。

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

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