6

これを読み続ける前に、コピー初期化と直接初期化の間に C++ に違いはありますか?を読んでください。まず、それが何について話しているのかを理解していることを確認してください。

最初にここでルールを要約します (標準 n3225 8.5/16、13.3.1.3、13.3.1.4、および 13.3.1.5 を読んでください)。

1) 直接初期化の場合、すべてのコンストラクターはオーバーロード セットと見なされ、オーバーロード解決はオーバーロード解決ルールに従って最適なものを選択します。

2) コピーの初期化の場合、コピー元の型がコピー先の型と同じか、コピー先の型から派生したものである場合、ルールは上記と同じですが、変換するコンストラクター (明示的なコンストラクターがないコンストラクター) のみがオーバーロード セットと見なされる点が異なります。これは実際には、明示的なコピー/移動コンストラクターがオーバーロード セットと見なされないことを意味します。

3) 上記 (2) に含まれないコピーの初期化のケース (ソースの型が宛先の型とは異なり、宛先の型から派生していない) については、まず、ソースの型から宛先の型に変換できるユーザー定義の変換シーケンスを検討するか、または (変換関数を使用する場合) をその派生クラスに変換します。変換が成功した場合、結果は宛先オブジェクトを直接初期化するために使用されます。

3.1) このユーザー定義の変換シーケンスでは、8.5/16 および 13.3.1.4 の規則に従って、変換する ctor (非明示的な ctor) と非明示的な変換関数の両方が考慮されます。

3.2) 結果の prvalue は、(1) に記載されている規則に従って、宛先オブジェクトを直接初期化します。8.5 /16 を参照してください。

さて、ルールについては十分です。いくつかの奇妙なコードを見てみましょう。私の推論がどこで間違っているのか、または単にすべてのコンパイラが間違っているのか、まったくわかりません。助けてください、ありがとう。

struct A
{
    A (int) { }
    A() { }
    explicit A(const A&) { }
};
struct B
{
    operator A() { return 2; }
    //1) visual c++ and clang passes this
    //gcc 4.4.3 denies this, says no viable constructor available
};
int main()
{
    B b;
    A a = b;
    //2) oops, all compilers deny this
}

私の理解では、(1)については、

operator A() { return 2; }

C++ には関数 return をコピー初期化と見なす規則があるため、上記の規則に従って、2 は最初に暗黙的に A に変換されますが、A にはコンストラクタ A(int) があるため、これは問題ないはずです。次に、変換された一時的な prvalue を使用して、返されたオブジェクトを直接初期化します。直接初期化では明示的なコピー コンストラクターを使用できるため、これも問題ありません。したがって、GCCは間違っています。

(2)については、

A a = b;

私の理解では、最初に b は演算子 A() によって暗黙的に A に変換され、次に変換された値を使用して a を直接初期化します。これはもちろん明示的なコピー コンストラクターを呼び出すことができますか? したがって、これはコンパイルに合格するはずで、すべてのコンパイラが間違っていますか?

(2) の場合、Visual C++ と clang の両方に「エラー、B から A に変換できません」のようなエラーがありますが、A のコピー コンストラクターで明示的なキーワードを削除すると、エラーはなくなります。

読んでくれてありがとう。


編集 1

誰かがまだ私の意図を理解していないので、8.5/16 から次の標準を引用します。

それ以外の場合 (つまり、残りのコピー初期化の場合)、ソース型から宛先型、または (変換関数が使用されている場合) その派生クラスに変換できるユーザー定義の変換シーケンスは、13.3 で説明されているように列挙されます。 1.4 であり、過負荷解決 (13.3) によって最適なものが選択されます。変換ができないか、あいまいな場合は、初期化の形式が正しくありません。選択された関数は、初期化式を引数として呼び出されます。関数がコンストラクターの場合、呼び出しは、目的の型の cv 修飾されていないバージョンの一時を初期化します。一時的なものは prvalue です。呼び出しの結果 (コンストラクターの場合は一時的なもの) は、上記の規則に従って、直接初期化するために使用されます。コピー初期化の宛先であるオブジェクト。場合によっては、中間結果を初期化されるオブジェクトに直接構築することにより、この直接初期化に固有のコピーを排除する実装が許可されます。12.2、12.8を参照。

ユーザー定義の変換後に直接初期化することについて言及していることに注意してください。つまり、私の理解では、次のコードは、私がコメントしたようにルールに従う必要があります。これは、clang、coomeau online、visual c++ の両方で確認されていますが、GCC 4.4.3 は (1) と (2) の両方で失敗します。これは奇妙なルールですが、標準からの推論に従います。

struct A
{
    A (int) { }
    A() { }
    explicit A(const A&) { }
};

int main()
{
    A a = 2;    //1)OK, first convert, then direct-initialize
    A a = (A)2; //2)oops, constructor explicit, not viable here!
}
4

2 に答える 2

8

コピー コンストラクターを宣言しましたexplicit(ところで、なぜですか?)。これは、クラス オブジェクトの暗黙的なコピーに使用できなくなったことを意味します。このコンストラクターをコピーに使用するには、直接初期化構文を使用する必要があります。12.3.1/2 を参照

2 明示的なコンストラクターは、非明示的なコンストラクターと同様にオブジェクトを構築しますが、直接初期化構文 (8.5) またはキャスト (5.2.9、5.4) が明示的に使用されている場合にのみ構築します。

この問題は、次のはるかに短い例で説明できます

struct A {
  A() {}
  explicit A(const A&) {}
};

int main() {
  A a;
  A b = a; // ERROR: copy-initialization
  A c(a); // OK: direct-initialization
}

これは、すべての変換がコピー初期化に依存しており、それが暗黙のコピーに依存しているため、すべての変換が機能しなくなる原因です。そして、暗黙のコピーを無効にしました。

さらに、この特定の問題をカバーするDefect Report #152を参照してください。「提案された解決策」の結果がどうなるかはわかりませんが...

于 2011-01-26T03:38:12.073 に答える
0

あなたが言ったように、gccは次のコードをコンパイルしません。

struct A {
  A( int ) {}
  explicit A( A const& ) {}
};

int main() {
  A a = 2;
}

これは、現在の標準8.5p15による標準準拠ではないと思います。
ただし、最初のケースについては、

struct A {
  A( int ) {}
  explicit A( A const& ) {}
};

struct B {
  operator A() { return 2; }
};

int main() {
  B b;
  A a = b;
}

これを許可することが適合であると私は確信していません。
ご存知かもしれませんが、return概念的に2回コピーを呼び出します。2から暗黙的に
return 2;構築し、一時的なreturn-value(R)を直接初期化するために使用されます。 ただし、Rから?への2回目のコピーには直接初期化を適用する必要があります。 現在の標準では、直接初期化を2回適用する必要があることを明示的に示している表現が見つかりませんでした。 このシーケンスはある意味で仕様を損なうことは確かなので、コンパイラ開発者がこれを許可することは欠陥だと思っていても驚かない。Aint
a

explicit

不要な追加をさせていただきます。
コンパイラの不適合な動作は、コンパイラに直接欠陥があることを意味するものではありません。
欠陥レポートが示すように、標準にはすでに欠陥があります。
たとえば、C標準では、T型の配列へのポインターから、Tの配列へのポインターへの変換は許可されていませんconst
これは、C ++で許可されており、Cでも同様に意味的に許可されるべきだと思います。
Gccは、この変換に関する警告を発行します。Comeauはエラーを発行し、コンパイルしません。

于 2011-01-26T14:50:05.400 に答える