2013-07-17 12 views
8

g ++(4.7または4.8)がコンパイルを拒否するC++ 11コードの小片を持っていて、B2 b2a(x、 {P(y)})はあいまいです。 Clang ++はこのコードに満足していますが、g ++がコンパイルするのが完全にうれしいB2 b2b(x、{{P(y)}}をコンパイルすることを拒否しています!C++ 11のコンストラクタのオーバーロードの解決とinitialiser_lists:clang ++とg ++の同意がない

両方のコンパイラは、{...}または{{...}}を引数としてB1コンストラクタに完全に満足しています。どんなC++言語弁護士がどのコンパイラが正しいか(もしあれば)、何が起こっているのかを説明できますか?以下のコード:

#include <initializer_list> 

using namespace std; 

class Y {}; 
class X; 

template<class T> class P { 
public: 
    P(T); 
}; 

template<class T> class A { 
public: 
    A(initializer_list<T>); 
}; 

class B1 { 
public: 
    B1(const X&, const Y &); 
    B1(const X&, const A<Y> &); 
}; 

class B2 { 
public: 
    B2(const X &, const P<Y> &); 
    B2(const X &, const A<P<Y>> &); 
}; 

int f(const X &x, const Y y) { 
    B1 b1a(x, {y}); 
    B1 b1b(x, {{y}}); 
    B2 b2a(x, {P<Y>(y)}); 
    B2 b2b(x, {{P<Y>(y)}}); 
    return 0; 
} 

とコンパイルエラー、打ち鳴らす:

$ clang++ -stdlib=libc++ -std=c++11 test-initialiser-list-4.cc -o test.o -c 
test-initialiser-list-4.cc:32:6: error: call to constructor of 'B2' is ambiguous 
    B2 b2(x, {{P<Y>(y)}}); 
    ^~~~~~~~~~~~~~~ 
test-initialiser-list-4.cc:26:5: note: candidate constructor 
    B2(const X &, const P<Y> &); 
    ^
test-initialiser-list-4.cc:27:5: note: candidate constructor 
    B2(const X &, const A<P<Y>> &); 
    ^

グラム++:

test-initialiser-list-4.cc: In function 'int f(const X&, Y)': 
test-initialiser-list-4.cc:32:21: error: call of overloaded 'B2(const X&, <brace-enclosed initializer list>)' is ambiguous 
    B2 b2(x, {P<Y>(y)}); 
        ^
test-initialiser-list-4.cc:32:21: note: candidates are: 
test-initialiser-list-4.cc:27:5: note: B2::B2(const X&, const A<P<Y> >&) 
    B2(const X &, const A<P<Y>> &); 
    ^
test-initialiser-list-4.cc:26:5: note: B2::B2(const X&, const P<Y>&) 
    B2(const X &, const P<Y> &); 
    ^

これはテンプレートで過負荷に均一な初期化、初期化子リストの構文と機能の間の相互作用のようなにおい議論(私はg ++がかなり厳しいと知っています)しかし、私はここで正しい振る舞いでなければならないものを解くことができる標準弁護士ではありません!

+1

2つのローカル変数と呼ばちょうど誤植と、問題の原因ではありません、私は確かに – astraujums

+0

...と仮定します!修正済み... –

+1

initializer_listから 'A'を作成し、' T'から 'P'を作成するのはまったく一致するはずなので、あいまいさがあります。私が説明できないことは、コンパイラが不平を言うために別のものを選ぶ理由です。 –

答えて

5

最初のコードは、私は何が起こるべきだと思います。 (以下では、最初のパラメータは無視します。なぜなら、2番目のパラメータだけに興味があるからです。最初のパラメータは、例では常に完全に一致します)。ルールは現在、仕様に流通しているので、他のコンパイラにはバグがあるとは言いません。

B1 b1a(x, {y}); 

Yが集合体であるとYは何のデータ(もちろん)タイプYのメンバーまたはそれによって初期化可能何か他のもの(これは醜いものですがないため、このコードは、C++ 11でconst Y&コンストラクタを呼び出すことはできませんC++ 14 CDにはまだこれが書かれていないので、最終的なC++ 14にこの修正が含まれるかどうかはわかりません)。

const A<Y>&パラメータを持つコンストラクタを呼び出すことができます - {y}A<Y>のコンストラクタの引数とさせていただき、そのコンストラクタのstd::initializer_list<Y>を初期化します。

したがって、第2のコンストラクターは正常にと呼ばれます。ここで

B1 b1b(x, {{y}}); 

、基本的に同じ引数がconst Y&パラメータを持つコンストラクタのための数をカウントします。

パラメータタイプがconst A<Y>&のコンストラクタでは、少し複雑です。 std::initializer_list<T>を初期化するコストを計算する過負荷解決の変換コストのルールでは、ブレースされたリストのすべての要素がTに変換可能である必要があります。しかし、以前は、{y}Y(集約されているため)に変換することはできません。 std::initializer_list<T>が集合体であるかどうかを知ることが重要になります。率直に言って、のアイデアはありません標準ライブラリ句に従って集合体であるとみなされなければならないかどうか。

もし我々が非集約であるとすれば、std::initializer_list<Y>というコピーコンストラクタを検討していますが、再び同じテストシーケンスがトリガされます(オーバーロードの解決チェックでは無限再帰につながります)。これはむしろ奇妙で実装不可能なので、私は実装がこのパスを取るとは思わない。

std::initializer_listを集計すると、「いいえ、変換が見つかりませんでした」と表示されます(上記集計の問題を参照)。その場合は、イニシャライザコンストラクタを単一のイニシャライザリスト全体で呼び出すことができないため、は複数の引数に分割され、コンストラクタはA<Y>に分割されます。そのため、この場合、{y}std::initializer_list<Y>という単一のパラメータとして初期化されます。これは完全にうまく動作し、魅力的に機能します。

したがってstd::initializer_list<T>が集約であると仮定して、はこれが問題ではなく、2番目のコンストラクタを正常にと呼びます。この場合、次の場合には

B2 b2a(x, {P<Y>(y)}); 

P<Y>は、ユーザー提供のコンストラクタを持っているので、私たちは、もうYと上記のような集約問題は発生しません。

P<Y>パラメータコンストラクタの場合、そのパラメータは{P<Y> object}で初期化されます。 P<Y>には初期化子リストがないため、リストは個々の引数に分割され、P<Y>の移動コンストラクタをP<Y>のrvalueオブジェクトで呼び出します。 A<P<Y>>パラメータコンストラクタ

、それは{y}によって初期化A<Y>と上記の場合と同様である:std::initializer_list<P<Y>>{P<Y> object}によって初期化することができるので、引数リストが分割されていない、従ってブレースは、コンストラクタのstd::initializer_list<T>を初期化するために使用されます。

ここで、両方のコンストラクタが正常に動作します。これらはここではオーバーロードされた関数のように動作し、両方の場合の2番目のパラメータはユーザー定義の変換を必要とします。どちらの場合も同じ変換関数またはコンストラクタが使用されている場合にのみ、ユーザ定義の変換シーケンスを比較できます。ここではそうではありません。したがって、これはC++ 11(およびC++ 14 CD)ではあいまいです。我々は微妙なポイントを持ってここに

struct X { operator int(); X(){/*nonaggregate*/} }; 

void f(X); 
void f(int); 

int main() { 
    X x; 
    f({x}); // ambiguity! 
    f(x); // OK, calls first f 
} 

直感的な結果は、おそらく上記の集約初期化すごみを固定と同じランで固定されます。このカウンタは(両方とも最初のF呼ぶ)を探索します。これは、{x}->Xが恒等変換(X->xのように)になると言って実装されています。現在、ユーザー定義の変換です。

したがって、ここであいまいさはです。パラメータconst P<Y>&とコンストラクタの

B2 b2b(x, {{P<Y>(y)}}); 

、我々は再び、引数を分割しP<Y>のコンストラクタ(複数可)に渡された{P<Y> object}引数を取得します。 P<Y>にはコピーコンストラクタがあることに注意してください。しかし、ここで問題となるのは、ユーザーが定義した変換が必要なため、使用を許可されていないことです(13.3.3.1p4参照)。左の唯一のコンストラクタはYを取るものですが、Y{P<Y> object}で初期化できません。パラメータA<P<Y>>とコンストラクタの

{P<Y> object}(上記Y有する以外 - ダン、凝集)P<Y>に変換可能であるため、{{P<Y> object}}は、std::initializer_list<P<Y>>を初期化することができます。

したがって、第2のコンストラクターは正常にと呼ばれます。


std::initializer_list<T>が集合体であるという仮定の下で成功し

  • と呼ばれる、すべての4

    • 二コンストラクタの概要、これは素晴らしいですし、正常
    • を二コンストラクタを呼び出しますここのあいまいさ
    • 二コンストラクタが正常に機能「f」の名前を「B2B」と
  • +0

    ありがとう、私の混乱は完全に不当ではなかったように見えます:) –

    関連する問題