C++ Visual StudioプロジェクトをVS2017からVS2019に移行しています。
以前は発生していなかったエラーが発生しました。次の数行のコードで再現できます。
struct Foo
{
Foo() = default;
int bar;
};
auto test = Foo { 0 };
エラーは
(6):エラーC2440: '初期化中': '初期化リスト'から 'Foo'に変換できません
(6):注:コンストラクターがソース型を取得できないか、コンストラクターのオーバーロードの解決があいまいです
プロジェクトは/std:c++latest
フラグでコンパイルされます。 godbolt で再現しました。 /std:c++17
に切り替えると、以前と同じように正常にコンパイルされます。
clang と-std=c++2a
を使用して同じコードをコンパイルしようとすると、同様のエラーが発生しました。また、他のコンストラクタをデフォルト設定または削除すると、このエラーが発生します。
どうやら、いくつかの新しいC++ 20機能がVS2019に追加され、この問題の原因が https://en.cppreference.com/w/cpp/language/aggregate_initialization に記述されていると思います。そこでは、集約は(他の基準の中で)持っている構造体である可能性があると述べています
括弧内の「明示的にデフォルトまたは削除されたコンストラクターが許可されています」が削除され、「ユーザー指定」が「ユーザー宣言」に変更されたことに注意してください。
だから私の最初の質問は、標準のこの変更が私のコードが以前にコンパイルされたが、もうコンパイルされない理由であると私は正しく想定していますか?
もちろん、これを修正するのは簡単です。明示的にデフォルト設定されたコンストラクタを削除するだけです。
ただし、すべてのプロジェクトで非常に多くのコンストラクターを明示的にデフォルト設定して削除しました。暗黙的にデフォルト設定または削除されたコンストラクターよりもサプライズが少なくなるため、この方法でコードをより表現力のあるものにすることは良い習慣であることがわかりました。しかし、この変更により、これはそれほど良い習慣ではなくなったように見えます...
だから私の実際の質問は:C++ 17からC++ 20へのこの変更の背後にある理由は何ですか?この後方互換性の中断は意図的に行われたのですか? 「わかりました、ここでは下位互換性を壊していますが、それはより大きな利益のためです」のようなトレードオフはありましたか?これより良いことは何ですか?
P1008 からの要約、変更につながった提案:
C++は現在、ユーザー宣言されたコンストラクターを持ついくつかの型を、それらのコンストラクターをバイパスして、集約初期化を介して初期化することを許可しています。その結果、意外で混乱を招き、バグの多いコードになります。このホワイトペーパーでは、C++での初期化セマンティクスをより安全に、より均一に、そして簡単に教えるための修正を提案します。また、この修正により導入される重大な変更についても説明します。
それらの例の1つは次のとおりです。
struct X { int i{4}; X() = default; }; int main() { X x1(3); // ill-formed - no matching c’tor X x2{3}; // compiles! }
私には、提案された変更が、それらがもたらす後方非互換性の価値があることは明らかです。そして実際、= default
のデフォルトコンストラクターを集約することは、もはや良い習慣ではないようです。
P1008からの推論(PDF) は2つの方向から最もよく理解できます。
集約の一般的な概念は、「コンストラクターのないクラス」です。 Typename() = default;
がクラス定義内にある場合、ほとんどの人はそれがコンストラクターを持っていると見なします。これは標準のデフォルトコンストラクターのように動作しますが、型にはまだコンストラクターがあります。それは多くのユーザーからのアイデアの広い概念です。
集約は純粋なデータのクラスであると想定されており、任意のメンバーが指定された任意の値を想定することができます。その観点からは、たとえデフォルトに設定したとしても、いかなる種類のコンストラクターも提供することはできません。これにより、次の理由がわかります。
最も明白な答えは、デフォルトのコンストラクタである_= default
_です。私はおそらくグループ1のメンバーだからです。明らかに、それはうまくいきません。
C++ 20より前のオプションは、クラスに他のコンストラクターを提供するか、特別なメンバー関数の1つを実装することです。これらのオプションはどちらも口当たりが良いわけではありません。(定義により)実際には実装する必要がないためです。副作用を起こすためにやっているだけです。
C++ 20以降では、明白な答えが機能します。
このようにルールを変更することで、集約と非集約visibleの違いが生まれます。集合体にはコンストラクタがありません。したがって、型を集合体にしたい場合は、コンストラクターを指定しないでください。
おお、そしてここに面白い事実があります:C++ 20以前、これは集合体です:
_class Agg
{
Agg() = default;
};
_
デフォルトのコンストラクタはprivateであるため、Agg
へのプライベートアクセス権を持つユーザーのみが呼び出すことができます... _Agg{}
_、コンストラクタをバイパスし、完全に合法です。
このクラスの明確な目的は、コピーできるクラスを作成することですが、その初期構築はプライベートアクセスを持つクラスからのみ取得できます。これにより、Agg
が指定されたコードのみがAgg
をパラメーターとして取る関数を呼び出すことができるため、アクセス制御の転送が可能になります。そして、Agg
にアクセスできるコードだけが作成できます。
または、少なくとも、それが想定されている方法です。
これで、デフォルト/削除されたコンストラクターがパブリックに宣言されていない場合は集合体であると言うことで、これをより的を絞って修正できます。しかし、それはさらに一致しているように感じます。目に見えるように宣言されたコンストラクタを持つクラスは、集合体である場合もあれば、そうでない場合もあります。
実際、MSDNは以下のドキュメントであなたの懸念に対処しました:
Visual Studio 2019では、/ std:c ++ latestの下で、ユーザーが宣言したコンストラクター(たとえば、= defaultまたは= deleteとして宣言されたコンストラクターを含む)を持つクラスは、集約ではありません。以前は、ユーザーが指定したコンストラクターのみが、クラスを集合体にする資格を失っていました。この変更により、そのような型を初期化する方法に追加の制限が適用されます。