変数を constexpr として宣言すると、コンパイラはコンパイル時に変数を評価します。これは、組み込み型だけでなく、ユーザー定義型のインスタンス化にも当てはまります。コンパイル時にオブジェクトを評価するには、いくつかの重大な制限があります。
簡単にするために、bool、char、int、double などの組み込み型を使用します。残りのデータ型をユーザー定義データ型と呼びます。これらは、たとえば、std::string、C++ ライブラリの型、およびユーザー定義のデータ型です。ユーザー定義型は通常、組み込み型を保持します。
変数
キーワード constexpr を使用すると、変数は定数式になります。
constexpr double myDouble= 5.2;
したがって、定数式を必要とするコンテキストで変数を使用できます。たとえば、配列のサイズを定義したい場合。これはコンパイル時に行う必要があります。
constexpr 変数の宣言については、いくつかのルールに留意する必要があります。
変数
- 暗黙的に const です。
- 初期化する必要があります。
- 初期化には定数式が必要です。
ルールは理にかなっています。コンパイル時に変数を評価すると、変数はコンパイル時に評価できる値にのみ依存できます。
オブジェクトは、コンストラクターの呼び出しによって作成されます。コンストラクターにはいくつかの特別な規則があります。
ユーザー定義型
constexpr を使用した定数式のポストからのクラス MyDistance は、コンパイル時に初期化されるすべての要件を満たします。しかし、要件は何ですか?
constexpr コンストラクターは、定数式でのみ呼び出すことができます。
<オール>constexpr ユーザー定義型
<オール>申し訳ありませんが、詳細はさらに難しいです:cppreference.com.理論を明確にするために、クラス MyInt を定義します。 MyInt は、今述べた点を示しています。このクラスにはさらに constexpr メソッドがあります。 constexpr メソッドと関数には特別な規則があります。これらの規則は次の記事で説明されるため、この記事では変数とユーザー定義型に関する重要事項に集中できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | // userdefinedTypes.cpp #include <iostream> #include <ostream> class MyInt{ public: constexpr MyInt()= default; constexpr MyInt(int fir, int sec): myVal1(fir), myVal2(sec){} MyInt(int i){ myVal1= i-2; myVal2= i+3; } constexpr MyInt(const MyInt& oth)= default; constexpr MyInt(MyInt&& oth)= delete; constexpr int getSum(){ return myVal1+myVal2; } friend std::ostream& operator<< (std::ostream &out, const MyInt& myInt){ out << "(" << myInt.myVal1 << "," << myInt.myVal2 << ")"; return out; } private: int myVal1= 1998; int myVal2= 2003; }; int main(){ std::cout << std::endl; constexpr MyInt myIntConst1; MyInt myInt2; constexpr int sec= 2014; constexpr MyInt myIntConst3(2011,sec); std::cout << "myIntConst3.getSum(): " << myIntConst3.getSum() << std::endl; std::cout << std::endl; int a= 1998; int b= 2003; MyInt myInt4(a,b); std::cout << "myInt4.getSum(): " << myInt4.getSum() << std::endl; std::cout << myInt4 << std::endl; std::cout << std::endl; // constexpr MyInt myIntConst5(2000); ERROR MyInt myInt6(2000); std::cout << "myInt6.getSum(): " << myInt4.getSum() << std::endl; std::cout << myInt6 << std::endl; // constexpr MyInt myInt7(myInt4); ERROR constexpr MyInt myInt8(myIntConst3); std::cout << std::endl; int arr[myIntConst3.getSum()]; static_assert( myIntConst3.getSum() == 4025, "2011 + 2014 should be 4025" ); } |
クラス MyInt には 3 つのコンストラクターがあります。 constexpr デフォルト コンストラクター (8 行目) と、2 つ (9 行目) と 1 つの引数を取るコンストラクター (10 行目)。 2 つの引数を持つコンストラクターは constexpr コンストラクターです。したがって、その本体は空です。これは、引数が 1 つの非 constexpr コンストラクターには当てはまりません。定義は、デフォルトの copy-constructor (15 行目) と削除された move-constructor (16 行目) に続きます。さらに、このクラスには 2 つのメソッドがありますが、メソッド getSum だけが const 式です。変数 myVal1 と myVal2 (26 行目と 27 行目) は、constexpr オブジェクトで使用する場合にのみ 2 つの方法で定義できます。最初に、コンストラクターの初期化リストでそれらを初期化できます (9 行目)。次に、クラス本体でそれらを初期化できます (26 行目と 27 行目)。コンストラクターの初期化リストの初期化の優先度が高くなります。コンストラクターの本体で両方の変数を定義することはできません (11 行目と 12 行目)。
理論を実践するために、プログラムの出力を次に示します。
このプログラムには、いくつかの特別な点が示されています:
- 実行時に constexpr コンストラクタを使用できます。もちろん、インスタンスは定数式ではありません (36 行目と 46 行目)。
- 非定数式を constexpr として宣言すると、コンパイラ エラーが発生します (52 行目と 57 行目)。
- constexpr コンストラクターは、非 constexpr コンストラクターと共存できます。同じことがクラスのメソッドにも当てはまります。
重要な観察事項は次のとおりです。constexpr オブジェクトは constexpr メソッドのみを使用できます。
しかし、やめてください。 main 関数の最後の 2 行 62 と 63 についての話は何ですか?
証明
非常に簡単です。これらは、myIntConst3.getSum() の呼び出しがコンパイル時に実行されることの 2 つの証拠です。
最初に、C++ では、配列のサイズが定数式でなければならないことが要求されます。次に、static_assert はコンパイル時にその式を評価します。そうでない場合、static_assert はコンパイルされません。
63行目を置き換えると
static_assert( myIntConst3.getSum() == 4025, "2011 + 2014 should be 4025" );
static_assert( myIntConst4.getSum() == 4001, "1998 + 2003 should be 4001" );
、コンパイル エラーが発生します。
次は?
あなたはすでにそれを知っていると思います。次の投稿では、contexpr 関数について書きます。 C++11 には多くの制限があり、C++14 ではほとんどなくなります。 C++14 の constexpr 関数は、通常の関数のように感じます。もちろん、関数に関する私の主張は、クラスのメソッドにも当てはまります。