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