C++11:定数式の一般化とその保障:constexpr

C++11では、定数式を定義できるようになった。

enum class Flag: unsigned int { good = 1, fail = 2, bad = 4, eof = 8 };

constexpr Flag operator|(Flag x, Flag y)
{
    return static_cast<Flag>(
        static_cast<unsigned int>(x) | static_cast<unsigned int>(y));
}

void func(Flag f)
{
    switch ( f ) {
    case Flag::bad:           /* ... */ break;
    case Flag::eof:           /* ... */ break;
    case Flag::bad|Flag::eof: /* ... */ break;
    default:                  /* ... */ break;
    }
}

定数式には、コンパイル時に不明な値は利用できない。

int x1 = 7;
constexpr int x2 = 7;
constexpr int x3 = x1; // エラー:初期化子が定数式でない
constexpr int x4 = x2; // OK

constexpr関数は、単純でなければならない。具体的には、return文は一つだけ許され、局所変数や選択文(if文など)や繰り返し文(for文など)は認められない。また、副作用を持ってもいけない。
ただし、条件式と再帰は使えるので、本当に必要であればあらゆるものをconstexpr関数にできる。(これらの制限の一部は、C++14で緩和された。)

constexpr int fact(int n)
{
    return (n > 1) ? n * fact(n - 1) : 1;
}

関数定義におけるconstexprは、コンパイル時に評価できることを示し、オブジェクト定義におけるconstexprは、初期化子をコンパイル時に評価する必要があることを示す。
なお、constexpr関数は、非定数の実引数を与えて呼び出すこともできるので、非定数用に同じ内容の関数を定義する必要はない。

void func(int n)
{
    int f5 = fact(5);            // コンパイル時に評価される可能性がある
    int fn = fact(n);            // 実行時に評価される

    constexpr int f6 = fact(6);  // コンパイル時に評価される
    constexpr int fnn = fact(n); // エラー:コンパイル時に評価できない
}

constexprは、クラスのコンストラクターに付けることもできるが、コンストラクターの本体は空でなければならず、メンバー初期化子並びかオブジェクトの初期化子で全てのメンバー変数を初期化しなければならない。

struct Point
{
    int x, y;
    constexpr Point(int xx = 0, int yy = 0): x{xx}, y{yy} {}
};

constexpr Point origin;
constexpr int ox{origin.x};
constexpr Point points[]{Point{1,2}, Point{3,4}, Point{5,6}};
constexpr int x{points[1].x};

constexprコンストラクターを持つクラスは、リテラル型と呼ばれる。

constexprは、全てのconstを置き換えるものではないことに注意しよう。

  • constexprは、「式をコンパイル時に評価する」ということを示し、主として定数の指定に用いる。
  • constは、「値を変更しない」ということを示し、主としてインターフェイスの指定に用いる。