読者です 読者をやめる 読者になる 読者になる

C++14のVariable Templatesの未定義動作かもしれない使い方

この記事はAmusementCreatorsAdventCalendar(通称ACAC)の5日目の記事です.

C++14で追加されたVariable Templatesという機能を使って色々してみたいと思います.
C++14とは2014年に公開された(ことになっている)C++という言語の規格です.

そもそもVariable Templatesとは

Variable Templatesは名の通り変数テンプレートです. 例えば

template<typename T>
constexpr auto PI = static_cast<T>(3.141592);

などとしておくとPI<double>とすればdouble型の円周率,PI<float>とすればfloat型の円周率として使えます.
double一つを引数に持つconstexprなコンストラクタを持つリテラル型Decimalと定義した時,PI<Decimal>と書いてコンパイル時に俺俺クラスの円周率を扱うとかもできます.

まぁC++14以前から使える関数テンプレートを使って

template<typename T>
constexpr auto PI() {
    return static_cast<T>(3.141592);
}

としてPI<double>()などと書いても同じことなのですが括弧が一つ少なくて済んで嬉しいみたいな感じです.

Variable Templatesを使ってフィボナッチ数を求める

こんなコードを書いてみます

#include <iostream>

template<int N>
auto fib = fib<N-1> + fib<N-2>;

template<>
auto fib<0> = 1;

template<>
auto fib<1> = 1;

int main() {
    std::cout << fib<5> << std::endl;
}

g++ 6.1.0でコンパイルして実行すると5番目のフィボナッチ数である8が出力されるはずです!
template引数をコンパイル時に渡される関数の引数とみなすことで変数テンプレートを関数の様に扱っているわけです.
テンプレートの特殊化がパターンみたいに見えて良さそげな気がしないこともありません.

さて,このコードをclang++3.8.0でコンパイルしてみましょう.なぜか0が出力されてませんか?
なんか変数テンプレートを再帰的に使っているのが悪いんですかね….
コンパイラの最適化バグか未定義動作を踏んでいる臭いがプンプンします.

一応このように副作用を生じさせるとclangでも期待通りの結果が得られます.

#include <iostream>

int dummy = 0;

template<int N>
auto fib = (++dummy, fib<N-1> + fib<N-2>);

template<>
auto fib<0> = 1;

template<>
auto fib<1> = 1;

int main() {
    std::cout << fib<5> << std::endl;
}

おまけ

#include<iostream>

template<int x, int y>
auto pixel = (std::cout << "(" << x << "," << y << ")" << std::endl, 1);

template<int width, int height, int x=0, int y=0>
auto image = (image<width/2, height/2, x, y>,
              image<width/2, height/2, x+width/2, y>,
              image<width/2, height/2, x, y+height/2>,
              image<width/2, height/2, x+width/2, y+height/2>);

template<int x, int y>
auto image<0, 0, x, y> = pixel<x, y>;

int main() {
    image<4, 4>;
}

これをコンパイルして実行するとwarningが沢山出てきた後に  { \{ (x, y) \mid 0 \leq x \lt 4 , \space 0 \leq y \lt 4 \} }の各格子点の座標が一つずつ出力されたと思います.
imageのテンプレート引数は左下の座標が (x, y) で横幅width縦幅heightの矩形範囲を表しています.それを左上,右上,左下,右下の四つに分割して再帰的に処理することで,テンプレート再帰深度を横幅と縦幅の積に対する対数に保っています.
そしてwidthとheightがともに0であればそれはつまり点なので変数テンプレートpixelに渡してpixelで出力するものを決めています.
auto pixel = ...の部分をいい感じにして4よりもっと大きな数をしていすれば変数テンプレートでレイトレーシングとか出来るんじゃないかなぁと思っているけど時間が超かかる上にメモリバカ食いしそう…

最後に

良い子のみんなはマネしないように!!