web-dev-qa-db-ja.com

なぜこのあいまいさがここにあるのですか?

次の最小限のコードがあると考えてください。

_#include <boost/type_traits.hpp>

template<typename ptr_t>
struct TData
{
    typedef typename boost::remove_extent<ptr_t>::type value_type;
    ptr_t data;

    value_type & operator [] ( size_t id ) { return data[id]; }
    operator ptr_t & () { return data; }
};

int main( int argc, char ** argv )
{
    TData<float[100][100]> t;   
    t[1][1] = 5;
    return 0;
}
_

GNU C++は私にエラーを与えます:

_test.cpp: In function 'int main(int, char**)':
test.cpp:16: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for second:
test.cpp:9: note: candidate 1: typename boost::remove_extent<ptr_t>::type& TData<ptr_t>::operator[](size_t) [with ptr_t = float [100][100]]
test.cpp:16: note: candidate 2: operator[](float (*)[100], int) <built-in>
_

私の質問は:

  1. GNU C++でエラーが発生しますが、Intel C++コンパイラではエラーが発生しないのはなぜですか?
  2. _operator[]_を次のように変更すると、エラーなしでコンパイルできるのはなぜですか?

    value_type & operator [] ( int id ) { return data[id]; }

C++標準へのリンクを歓迎します。


私が見ることができるように、2つの変換パスがあります:

  1. (1)intから_size_t_および(2)operator[](size_t)
  2. (1)operator ptr_t&()、(2)int to _size_t_および(3)ビルドインoperator[](size_t)
35

それは実際には非常に簡単です。 t[1]の場合、オーバーロード解決には次の候補があります。

候補1(組み込み:13.6/13)(Tは任意のオブジェクトタイプ):

  • パラメータリスト:(T*, ptrdiff_t)

候補2(オペレーター)

  • パラメータリスト:(TData<float[100][100]>&, something unsigned)

引数リストは13.3.1.2/6によって与えられます:

過負荷解決の候補関数のセットは、メンバー候補、非メンバー候補、および組み込み候補の和集合です。引数リストには、演算子のすべてのオペランドが含まれています。

  • 引数リスト:(TData<float[100][100]>, int)

最初の引数が候補2の最初のパラメーターと正確に一致していることがわかります。ただし、候補1の最初のパラメーターにはユーザー定義の変換が必要です。したがって、最初のパラメーターでは、2番目の候補が優先されます。

また、2番目の位置の結果が異なることがわかります。いくつかの仮定を立てて、何が得られるかを見てみましょう。

  1. ptrdiff_tintです。最初の候補は完全に一致するため勝ちますが、2番目の候補は積分変換が必要です。
  2. ptrdiff_t is long:どちらも整数変換が必要なため、どちらの候補も勝ちません。

さて、13.3.3/1は言います

ICSi(F)が、リスト内のi番目の引数を実行可能な関数Fのi番目のパラメーターのタイプに変換する暗黙の変換シーケンスを表すとします。

実行可能な関数F1は、すべての引数iについてICSi(F1)がICSi(F2)よりも悪い変換シーケンスではない場合、別の実行可能な関数F2よりも優れた関数であると定義され、次に...いくつかの引数jについて、ICSj( F1)はICSj(F2)よりも優れた変換シーケンスです。そうでない場合は...

最初の仮定では、候補2が最初のパラメーターで勝ち、候補1が2番目のパラメーターで勝つため、全体的な勝者は得られません。私はそれをcriss-crossと呼んでいます。 2番目の仮定では、どちらのパラメーターの変換も悪いので、候補2が全体的に勝ちますが、最初のパラメーターの変換はより良いでした。

最初の仮定では、2番目のパラメーターの整数変換(intからunsignedへ)が、最初のパラメーターの他の候補のユーザー定義の変換よりも悪ではないことは問題ではありません。十字架では、ルールは粗雑です。


その最後の点は、周りのすべての騒ぎのために、まだあなたを混乱させるかもしれないので、例を作ってみましょう

void f(int, int) { }
void f(long, char) { }

int main() { f(0, 'a'); }

0longに変換されるのは'a'からint-それでも、あなたは危機に瀕している状況にあるため、あいまいさを感じます。

次の式を使用します。

_t[1][1] = 5;
_

コンパイラは左側に焦点を当てて、そこに何が入るかを判別する必要があるため、lhsが解決されるまで_= 5;_は無視されます。式_t[1][1]_を残します。これは、2つの操作を表し、2番目の操作は最初の操作の結果を操作するため、コンパイラーは式の最初の部分のみを考慮する必要があります:_t[1]_ 。実際のタイプは_(TData&)[(int)]_です

TDataの_operator[]_は_size_t_引数をとるように定義されているため、呼び出しはどの関数とも完全には一致しません。そのため、これを使用するには_1_ intから_size_t_への暗黙的な変換。それが最初の選択です。現在、別の可能なパスは、ユーザー定義の変換を適用して_TData<float[100][100]>_を_float[100][100]_に変換することです。

intから_size_t_への変換は積分変換であり、標準の表9の変換ユーザー定義の変換_TData<float[100][100]>_から_float[100][100]_§13.3.3.1.2/ 4に従った変換。 _float [100][100]&_からfloat (*)[100]への変換は、表9でExact Matchとしてランク付けされています。コンパイラーは、これら2つの変換シーケンスから選択できません。

Q1:すべてのコンパイラが同じように標準に準拠しているわけではありません。ある特定のケースでは、コンパイラーが他のコンパイラーとは異なるパフォーマンスをすることを発見することは非常に一般的です。この場合、g ++の実装者は、コンパイラーが選択できない標準については駄目だと決めましたが、Intelの実装者はおそらく、好みの変換を黙って適用しただけでしょう。

Q2:ユーザー定義の_operator[]_の署名を変更すると、引数は渡された型と正確に一致します。 _t[1]_はt.operator[](1)と完全に一致し、変換はまったく行われないため、コンパイラーはそのパスに従う必要があります。

正確な答えはわかりませんが...

この演算子のため:

operator ptr_t & () { return data; }

[]をインデックスとして受け入れる組み込みのsize_t演算子(配列サブスクリプション)がすでに存在します。したがって、2つの[]演算子があり、組み込みであり、ユーザーが定義します。ブースはsize_tを受け入れるため、これはおそらく違法な過負荷と見なされます。

//編集
これは意図したとおりに機能するはずです

template<typename ptr_t>
struct TData
{
    ptr_t data;
    operator ptr_t & () { return data; }
};
1
adf88

式t [1] [1]の2つの候補を表示しようとしました。これらは両方とも等しいランク(変換)です。したがって、あいまいさ

ここでの落とし穴は、13.6/13に基づく組み込みの[]演算子が次のように定義されていることだと思います。

T& operator[](T*, ptrdiff_t);

私のシステムでは、ptrdiff_tは「int」として定義されています(x64の動作を説明していますか?)

template<typename ptr_t> 
struct TData 
{ 
    typedef typename boost::remove_extent<ptr_t>::type value_type; 
    ptr_t data; 

    value_type & operator [] ( size_t id ) { return data[id]; } 
    operator ptr_t & () { return data; } 
}; 

typedef float (&ATYPE) [100][100];

int main( int argc, char ** argv ) 
{ 
    TData<float[100][100]> t;    

    t[size_t(1)][size_t(1)] = 5; // note the cast. This works now. No ambiguity as operator[] is preferred over built-in operator

    t[1][1] = 5;                 // error, as per the logic given below for Candidate 1 and Candidate 2

    // Candidate 1 (CONVERSION rank)
    // User defined conversion from 'TData' to float array
    (t.operator[](1))[1] = 5;

    // Candidate 2 (CONVERSION rank)
    // User defined conversion from 'TData' to ATYPE
    (t.operator ATYPE())[1][1] = 6;

    return 0; 
}

編集:

これが私が思うことです:

候補1(演算子[])の場合、変換シーケンスS1はユーザー定義の変換-標準変換(intからsize_t)です。

候補2の場合、変換シーケンスS2はユーザー定義の変換-> intからptrdiff_t(最初の引数の場合)-> intからptrdiff_t(2番目の引数の場合)です。

変換シーケンスS1はS2のサブセットであり、より優れていると考えられます。しかし、ここにキャッチがあります...

ここでは、以下の標準からの引用が役立つはずです。

$ 13.3.3.2/3の状態-S1がS2の適切なサブシーケンスである場合、標準の変換シーケンスS1は標準の変換シーケンスS2よりも優れた変換シーケンスです(Lvalueを除く13.3.3.1.1で定義された正規形式の変換シーケンスと比較)変換;アイデンティティ変換シーケンスは、非アイデンティティ変換シーケンスのサブシーケンスと見なされます)、そうでない場合...

$ 13.3.3.2状態-"ユーザー定義の変換シーケンスU1は、同じユーザー定義の変換関数またはコンストラクターを含み、U1の2番目の標準変換シーケンスがU2の2番目の標準変換シーケンス。」

ここで、and条件の最初の部分 "同じユーザー定義の変換関数またはコンストラクターが含まれている場合"は適切ではありません。したがって、and条件の2番目の部分 "1の2番目の標準変換シーケンスがU2の2番目の標準変換シーケンスよりも優れている場合"が適切である場合でも、S1もS2も優先されません。その他。

そのため、gccのphantomエラーメッセージ「最初の最悪の変換が2番目の最悪の変換よりも優れているにもかかわらず、ISO C++はこれらがあいまいであると言っています」

これはあいまいさを説明します。

0
Chubsdad

過負荷の解決は頭痛の種です。しかし、例に固有すぎる修正(インデックスオペランドのoperator[]への変換を排除)に遭遇したため(リテラルはタイプintですが、使用するほとんどの変数はそうではありません) 、多分あなたはそれを一般化することができます:

template< typename IT>
typename boost::enable_if< typename boost::is_integral< IT >::type, value_type & >::type
operator [] ( IT id ) { return data[id]; }

残念ながら、GCC 4.2.1と4.5は--pedanticの下で苦情なしにサンプルを受け入れるため、これをテストすることはできません。これは、コンパイラのバグかどうかという疑問を実際に引き起こします。

また、Boostの依存関係を削除すると、Comeauを通過しました。

0
Potatoswatter

私にはそう思われます

t[1][1] = 5;

コンパイラはどちらかを選択する必要があります。

value_type & operator [] ( size_t id ) { return data[id]; }

intリテラルがsize_tに変換される場合、または

operator ptr_t & () { return data; }

その後に通常の配列インデックスが続きます。この場合、インデックスのタイプは完全に一致します。


エラーに関しては、コンパイラ拡張としてのGCCが最初のオーバーロードを選択したいと考えているようで、-pedanticおよび/または-Werrorフラグを使用してコンパイルしているため、標準のWordに準拠するように強制されます。

(私は衒学的な気分ではないので、特にこのトピックに関して、標準からの引用はありません。)

0
UncleBens