constexprは何度も解説しているが、だいぶ昔のドラフトを元にしていたので、正式な規格とはずれている。今書いている参考書はもうしばらく時間がかかるので、とりあえずその間をつなぐために、constexprについて解説する。
constexpr変数
constexpr指定子をつけてリテラル型の変数を宣言すると、その変数はコンパイル時定数になる。変数の初期化式はコンパイル時定数でなければならない。
void f() { constexpr int x = 10 ; // OK、コンパイル時定数 int r = 0 ; constexpr int error = r ; // エラー }
constexpr指定子をつけて関数を宣言すると、constexpr関数になる。constexpr関数は、コンパイル時に評価される。ただし、constexpr関数にはとても厳しい制限がある。
まず、仮引数の型と戻り値の型は、リテラル型でなければならない。virtual関数であってはならない。関数の本体は、=defaultか、=deleteか、あるいは複合文で、しかもその中身が極端に制限される。
constexpr int f() { ; // null文 static_assert( true, "..." ) ; // static_assert宣言 typedef int type1 ; // typedef宣言 using type2 = int ; // エイリアス宣言 using std::vector ; // using宣言 using namespace std ; // using directive return 0 ; // 唯一のreturn文 }
これ以外の文を書くことはできない。これでもだいぶ制限が緩和されたのだ。昔のドラフトでは、return文ひとつの他には何も書けなかった。
もちろん、変数の宣言なんてできない。(x+y)(x+y)を返すconstexpr関数は以下のようになる。
// エラー constexpr int error( int x, int y ) { constexpr int temp = x + y ; return temp * temp ; } // OK constexpr int ok( int x, int y ) { return (x + y) * (x + y) ; }
if文も使えないので、条件分岐は条件演算子や論理和や論理積を使うことになる。|x + y|を返すconstexpr関数は以下のようになる。
// エラー constexpr int error( int x, int y ) { constexpr int temp = x + y ; if ( temp < 0 ) return -temp ; else return temp ; } // OK constexpr int ok( int x, int y ) { return (x + y) < 0 ? -(x + y) : (x + y) ; }
もちろん、絶対値の計算を別のconstexpr関数で実装して丸投げすることは可能だ。
ループが使いたければ再帰だ。aのb乗(a,bは正の整数)を計算するconstexpr関数は以下のようになる。
// エラー constexpr unsigned long error( unsigned long a, unsigned long b ) { unsigned long result = 1 ; for ( ; b != 0 ; --b ) { result *= a ; } return result ; } // OK constexpr unsigned long ok( unsigned long a, unsigned long b ) { return b == 0 ? 1 : a * ok( a, b-1 ) ; }
この問題に関しては、中には再帰の方が分かりやすいと考える人もいるかもしれないが、あらゆるループを再帰で書くのは面倒だ。
現在、constexpr関数の制限を大幅に緩和して、普通に変数や任意の文を使えるようにすることが議論されている。
ちなみに、constexpr関数に対してコンパイル時定数以外を渡すと、実行時評価される。
constexpr int f( int x ) { return x ; } void g( int x ) { int result = f( x ) ; // 実行時評価 }
constexpr指定子をコンストラクターに指定すると、constexprコンストラクターとなる。constexprコンストラクターにはとても厳しい制限がある。constexpr関数の制限と似ているが、こちらはreturn文すら使えない。さらに、クラスのサブオブジェクトをすべて初期化しなければならない。この他にも厳しい制限が多数あるが、見事すべての制限を満たした暁には、なんとconstexprコンストラクターを持つクラスは、リテラル型になるのだ。
// クラスXはリテラル型 struct X { int x ; int y ; constexpr X( int x = 0, int y = 0 ) : x(x), y(y) { } } ;
リテラル型ということは当然、constexpr変数の型や、constexpr関数の仮引数や戻り値の型として使えるという事だ。
struct Integer { int member ; constexpr Integer( int value = 0 ) : member( value ) { } constexpr operator int() { return member ; } } ; constexpr Integer f( Integer i ) { return i + 1 ; } int main() { constexpr Integer i = 10 ; constexpr Integer result = f( i ) ; std::cout << result << std::endl ; }
つまり、constexprコンストラクターを持つクラスはコンパイル時定数として使えることを意味する。
とまあ、constexprは規格にして4ページ程度の小さな機能である。
No comments:
Post a Comment