2015-05-04

constexprで非定数式の状態を保持

Non-constant constant-expressions in C++

なんと、C++のconstexpr関数を呼び出すたびに戻り値を変える方法があるという。つまり、以下のstatic_assertが引っかかるコードだ。

int main ()
{
    constexpr int a = f ();
    constexpr int b = f ();

    static_assert (a != b, "fail");
}

なんと、constexpr関数は状態を保持できる計算能力を備えているというのだ。

fはconstexpr関数である。

読んでみたところ、要するにこうだ。

noexcept演算子はオペランドが定数式かどうかを判別するのに使える。

// exprが定数式であればtrue
constexpr bool b = noexcept( expr ) ;

未定義の関数は定数式ではない。

// 宣言
constexpr int f( int ) ;

void check1( )
{
    noexcept( f( 0 ) ) ; // false
}

// 定義
constexpr int f( int ) { return 0 ; }

void check2( )
{
    noexcept( f(0) ) ; // true
}

friend宣言は関数の定義を書くことができる。

constexpr int f( int ) ;

struct S
{
    friend constexpr int f( int ) { return 0 ; }
} ;

friend宣言で定義した関数はADL経由でしか呼び出せないが、それは問題ではない。friend宣言が現れて初めてconstexpr関数fが定義され、呼び出しが定数式になるということだ。

もし、frined宣言をするクラスをテンプレートにしたらどうなるだろうか。テンプレートが実体化した時だけ、関数fは定義されるということになる。

// 定義されたかどうかの1bitのフラグ
constexpr int flag (int);

// 関数を定義することでフラグ書き込む
template<class Tag>
struct writer {
  friend constexpr int flag (Tag) {
    return 0;
  }
};

// writerの実体化を遅延させるためのラッパー
template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };

// 実際の使い方
// 一回目に呼ばれた時点ではまだwriterが実体化しておらず、
// flag<int>は定義されていない
// 二度目以降に定義されるのでtrueとなる
template<
  bool B = noexcept (flag (0)),
  int    =   sizeof (dependent_writer<B>)
>
constexpr int f () {
  return B;
}
int main () {
  constexpr int a = f ();
  constexpr int b = f ();

  static_assert (a != b, "fail");
}

あとはこれを並べれば、何ビットでも状態が保持できる。

ドワンゴ広告

GWは長い。今週は休みだが、有給を申請し忘れたので木金は出社する。

ドワンゴは本物のC++プログラマーを募集しているようですが、休暇中なのでa要素を使ってリンクしません。

<a href="http://info.dwango.co.jp/recruit/">採用情報|株式会社ドワンゴ</a>

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

No comments: