• Class / Function / Type

      std::
    • Header file

      <>
    • Other / All

    最終更新日時(UTC):
    が更新

    履歴 編集

    constexpr関数内での条件分岐とループの文を許可 [N3652]

    このページはC++14に採用された言語機能の変更を解説しています。

    のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

    概要

    C++11で、汎用定数式の機能であるconstexprが導入された。

    constexpr関数には、コードの表現として強い制限があった。C++14では、以下のような制限緩和が行われた。

    • 変数宣言を許可
    • if文とswitch文を許可
    • 全てのループ文を許可(for文、範囲for文、while文、do-while文)
    • 変数の書き換えを許可
    • 戻り値型(リテラル型)として、voidを許可 / 戻り値型や関数引数で非const参照を許可
    • constexpr非静的メンバ関数の、暗黙のconst修飾を削除
    • constexprコンストラクタがbodyを持てるようになった

    仕様

    constexpr関数内での変数宣言を許可

    以下の例で示すように、constexpr関数内で変数を宣言できるようになった。

    constexpr int f()
    {
      int result = 0; // OK
                      // 関数f()自体がconstexprであるため、
                      // 変数resultはconstexprである必要はない。
      return result;
    }
    

    ただし、

    • staticthread_local記憶域の変数宣言は許可されない。
    • 未初期化変数の宣言は許可されない。

    constexpr関数内での条件分岐として、if文とswitch文を許可

    以下の例で示すように、constexpr関数内での条件分岐に、if文を使用できるようになった。else節も使用でき、else節を省略してもよい。

    constexpr int abs(int x)
    {
      if (x < 0) // OK
        return -x;
      return x;
    }
    

    また、switch文も使用できるようになった。

    enum class Weekday { Sun, Mon, Tue, };
    constexpr Weekday intToWeekday(int n)
    {
      switch (n) {
        case 0: return Weekday::Sun;
        case 1: return Weekday::Mon;
        case 2: return Weekday::Tue;
        default: break;
      }
      throw std::out_of_range("n is out of week");
    }
    

    ただし、goto文は許可されない。

    constexpr関数内で、全てのループ文を許可

    ループ文として、for文、範囲for文、while文、do-while文が許可された。

    constexpr int f()
    {
      int x = 0;
    
      // OK : for文
      for (int i = 0; i < 5; ++i) {
        x += i + 1;
      }
    
      // OK : 範囲for文
      int ar[] = {6, 7, 8};
      for (const int& i : ar) {
        x += i;
      }
    
      // OK : while文
      while (true) {
        x += 9;
        break;
      }
    
      // OK : do-while文
      do {
        x += 10;
      }
      while (false);
    
      return x;
    }
    

    constexpr関数内での、変数の書き換えを許可

    constexpr関数内において、ローカル変数、またはその関数が所属するクラスの非静的メンバ変数の書き換えが許可された。

    constexpr int square(int x)
    {
      x *= x; // OK : ローカル変数は書き換えてもよい
      return x;
    }
    

    struct X {
      int x;
    
      constexpr X(int x)
        : x(x) {}
    
      constexpr int square()
      {
        x *= x; // OK : 非静的メンバ変数も書き換えられる
        return x;
      }
    };
    
    constexpr int square(int n)
    {
      X x(n);
      return x.square();
    }
    

    constexpr関数の戻り値型として、voidを許可 / 戻り値型や関数引数で非const参照を許可

    constexpr関数での、パラメータの型、および戻り値の型は、リテラル型に分類される型に限定される。

    C++14では、リテラル型に分類される型に、voidが追加された。

    また、戻り値型や関数引数で非const参照を使うことが許可された。

    これにより、constexpr関数の戻り値型をvoidとし、非const参照のパラメータを書き換えて結果を返す、という操作が許可された。

    constexpr void square(int& x)
    {
      x *= x;
    }
    
    constexpr int f(int x)
    {
      square(x);
      return x;
    }
    

    constexpr非静的メンバ関数の、暗黙のconst修飾を削除

    C++11では、constexpr非静的メンバ関数は、暗黙でconstが付けられ、明示的にconstを付けることもできなかった:

    struct X {
      constexpr int f(); // これは以下と同じ
    //constexpr int f() const;
    };
    

    C++14ではこの仕様が削除され、constか非constかを、明示的に指定することになった。

    ※この変更によって、既存コードの互換性は壊れない。

    constexprコンストラクタがbodyを持てるようになった

    C++11では、constexprコンストラクタのbodyには以下の要素しか持たせることを許されていなかった:

    • ヌル文
    • static_assert
    • クラスや列挙型を定義しない、別の型名定義
    • using宣言と、usingディレクティブ

    これは事実上constexprコンストラクタのbodyが空でなければいけないことを意味している。

    C++14ではconstexprコンストラクタのbodyに関する制約は一般のconstexpr関数に従うようになったため、body内でローカル変数を定義したり引数に応じたメンバ変数の書き換えを行ったりすることが許可された。

    この機能が必要になった背景・経緯

    C++は直交性を重視して設計されており、直接関係ない機能同士を組み合わせて使用できる。しかし、C++11でのconstexprは、その制限によって、ほかの機能(インスタンス、forループ、変数書き換え、例外等)とうまく組み合わせられなかった。

    これらの制限を回避するために表現力を犠牲にしなければならず、プログラマをいらつかせていた。

    C++14では、constexpr関数、constexprメンバ関数、暗黙のconstといった制限を緩和する。

    関連項目

    参照