constexpr - 変数とオブジェクト

変数を constexpr として宣言すると、コンパイラはコンパイル時に変数を評価します。これは、組み込み型だけでなく、ユーザー定義型のインスタンス化にも当てはまります。コンパイル時にオブジェクトを評価するには、いくつかの重大な制限があります。

簡単にするために、bool、char、int、double などの組み込み型を使用します。残りのデータ型をユーザー定義データ型と呼びます。これらは、たとえば、std::string、C++ ライブラリの型、およびユーザー定義のデータ型です。ユーザー定義型は通常、組み込み型を保持します。

変数

キーワード constexpr を使用すると、変数は定数式になります。

constexpr double myDouble= 5.2;

したがって、定数式を必要とするコンテキストで変数を使用できます。たとえば、配列のサイズを定義したい場合。これはコンパイル時に行う必要があります。

constexpr 変数の宣言については、いくつかのルールに留意する必要があります。

変数

  • 暗黙的に const です。
  • 初期化する必要があります。
  • 初期化には定数式が必要です。

ルールは理にかなっています。コンパイル時に変数を評価すると、変数はコンパイル時に評価できる値にのみ依存できます。

オブジェクトは、コンストラクターの呼び出しによって作成されます。コンストラクターにはいくつかの特別な規則があります。

ユーザー定義型

constexpr を使用した定数式のポストからのクラス MyDistance は、コンパイル時に初期化されるすべての要件を満たします。しかし、要件は何ですか?

constexpr コンストラクターは、定数式でのみ呼び出すことができます。

<オール>
  • 例外処理を使用できません。
  • default または delete として宣言するか、関数本体を空にする必要があります (C++11)。
  • 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 関数は、通常の関数のように感じます。もちろん、関数に関する私の主張は、クラスのメソッドにも当てはまります。