1. ホーム
  2. c++

[解決済み] constexpr` と `const` の相違点

2022-03-14 19:19:14

質問

とはどう違うのですか? constexprconst ?

  • どちらか一方だけを使用できるのはどんな場合ですか?
  • 両方使えるのはどんなときで、どのように選べばいいのでしょうか?

どのように解決するのですか?

基本的な意味と構文

どちらのキーワードも、関数と同様にオブジェクトの宣言に使用することができます。基本的な違いは オブジェクト はこうです。

  • const としてオブジェクトを宣言しています。 定数 . これは、一度初期化されたオブジェクトの値は変化しないという保証を意味し、コンパイラはこの事実を利用して最適化を行うことができる。また、プログラマーが、初期化後に変更されることを意図していないオブジェクトを変更するようなコードを書くのを防ぐのにも役立つ。

  • constexpr で使用するのに適したオブジェクトであると宣言しています。 定数式 . しかし、以下のことに注意してください。 constexpr が唯一の方法というわけではありません。

に適用した場合 機能 基本的な違いは次のとおりです。

  • const は非静的メンバ関数にのみ使用でき、一般的な関数には使用できません。これは、メンバ関数が非静的データメンバを一切変更しないことを保証するものです (ただし、変更可能なデータメンバは例外で、どのみち変更される可能性があります)。

  • constexpr は、メンバ関数と非メンバ関数、およびコンストラクタで使用することができます。での使用に適した関数を宣言します。 定数式 . コンパイラは、その関数が特定の基準 (7.1.5/3,4) を満たしている場合にのみ、これを受け付けます。 (†) :

    • 関数本体は非仮想的で極めて単純でなければならない。型定義と静的アサートは別として、関数本体には単一の return ステートメントを使用することができます。コンストラクタの場合、初期化リスト、型定義、スタティックアサートのみが許される。( = default= delete も許されますが)。
    • C++14では、よりルールが緩和され、constexpr関数内でそれ以降許可されているもの。 asm 宣言に goto ステートメント以外のラベルを持つステートメントは casedefault トライブロック、非リテラル型の変数の定義、静的またはスレッド保存期間の変数の定義、初期化が行われない変数の定義。
    • 引数と戻り値の型は、必ず リテラル型 (すなわち、一般的に言えば、非常に単純な型、典型的にはスカラーまたは集合体)

定数表現

上述したように constexpr は、オブジェクトと関数の両方を定数式で使用するのに適したものとして宣言しています。定数式は、単に定数であるだけではありません。

  • テンプレートパラメータや配列サイズ指定子など、コンパイル時の評価が必要な場所で使用することができます。

      template<int N>
      class fixed_size_list
      { /*...*/ };
    
      fixed_size_list<X> mylist;  // X must be an integer constant expression
    
      int numbers[X];  // X must be an integer constant expression
    
    
  • でも、注意してください。

  • として何かを宣言する constexpr は、必ずしもコンパイル時に評価されることを保証するものではありません。それは を使用することができます。 がありますが、実行時に評価される他の場所でも使用することができます。

  • オブジェクト かもしれない 定数式で使用するのに適している がなければ 宣言されている constexpr . 例

         int main()
         {
           const int N = 3;
           int numbers[N] = {1, 2, 3};  // N is constant expression
         }
    
    

    これが可能なのは N は定数であり、宣言時にリテラルで初期化されるため、たとえ宣言されていなくても定数式の基準を満たします。 constexpr .

では、実際にどのような場合に constexpr ?

  • An オブジェクト のように N は定数表現として使用することができます。 なし と宣言されています。 constexpr . であるすべてのオブジェクトに当てはまります。
  • const
  • 積分型または列挙型の
  • 宣言時にそれ自体が定数式である式で初期化されます。

<サブ [これは§5.19/2によるものです:定数式は、[...]整数型または列挙型の[...]値でない限り、"値から値への変更を伴う部分式を含んではなりません; これがすべてのリテラル型について正しいという私の以前の主張を修正してくれたリチャード・スミスに感謝します]。

  • の場合 機能 を定数式で使用するのに適している場合、それは 必要なのは 明示的に宣言される constexpr 単に定数式関数の基準を満たすだけでは十分ではありません。例

     template<int N>
     class list
     { };
    
     constexpr int sqr1(int arg)
     { return arg * arg; }
    
     int sqr2(int arg)
     { return arg * arg; }
    
     int main()
     {
       const int X = 2;
       list<sqr1(X)> mylist1;  // OK: sqr1 is constexpr
       list<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr
     }
    
    

どのような場合に両方を使うことができますか/使うべきですか。 constconstexpr 一緒に?

A. オブジェクト宣言の中で 両方のキーワードが宣言される同じオブジェクトを参照している場合、これは決して必要ではありません。 constexpr を意味します。 const .

constexpr const int N = 5;

と同じです。

constexpr int N = 5;

ただし、キーワードがそれぞれ宣言の異なる部分を指している場合もあるので、注意が必要です。

static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

ここです。 NP はアドレス定数式、すなわちそれ自体が定数式であるポインタとして宣言されています。(これは、アドレスが静的/グローバル定数式にアドレス演算子を適用して生成される場合に可能です)。ここでは、両方の constexprconst が必要です。 constexpr は常に宣言されている式を参照します(ここでは NP であるのに対し const とは int (ポインタ-to-constを宣言している)。を削除すると const は式を不正にします(なぜなら (a) 非恒等式オブジェクトへのポインタは定数式になり得ないし、(b) &N は実際には定数へのポインタである)。

B. メンバ関数宣言の中で C++11の場合。 constexpr を意味します。 const C++14とC++17ではそうではありません。C++11で次のように宣言されたメンバ関数は

constexpr void f();

として宣言する必要があります。

constexpr void f() const;

として使用できるようにするため、C++14のもとで const 関数を使用します。