More Related Content Similar to constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ Similar to constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ(20) constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ3. ◆自己紹介
• 名前 : 村上 原野 (むらかみ げんや)
@bolero_MURAKAMI, id:boleros
• 棲息地: 大都会岡山
• 仕事 : 猪風来美術館陶芸指導員
・普段はろくろをまわしたり、
縄文土器をつくったりしています
・趣味は constexpr です
4. ◆自己紹介
• 公開しているライブラリ:
Sprout C++ Library (constexpr ライブラリ)
github.com/bolero-MURAKAMI/Sprout
• 過去の発表資料:
Boost.勉強会 #7
【中3⼥⼦でもわかる constexpr】
Boost.勉強会 #8
【中3⼥⼦が狂える本当に気持ちのいい constexpr】
Boost.勉強会 #12
【constexpr 中3⼥⼦テクニック】
江添とボレロ村上の京都C++勉強会
【すごい constexpr たのしくレイトレ!】
www.slideshare.net/GenyaMurakami
6. ◆導入
• constexpr とは
– 市⺠の義務
– constexpr を知らないで許されるのは小学生
まで
– C++14 は constexpr の時代
– コンパイル時処理だけじゃない constexpr
14. ◆constexpr の超基本
• constexpr 指定の変数
= コンパイル時定数
// コンパイル時定数
constexpr unsigned N = 10;
// コンパイル時定数は配列のサイズやテンプレート引数として使用可能
std::array<int, N> arr = {{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }};
15. ◆constexpr の超基本
• constexpr 指定の関数
= コンパイル時に呼出可能
• このような、コンパイル時に評価可能な
式を「定数式」という
// constexpr 関数
template<typename T>
constexpr T square(T const& t) {
return t * t;
}
// コンパイル時定数を求めることができる
constexpr int s = square(16);
static_assert(s == 256, “”);
16. ◆constexpr の超基本
[リテラル型] = constexpr で扱えるデータ型
[スカラー型] [リテラル型の配列]
LiteralType [N]
[参照型]
T&
[算術型]
[整数型] int, unsigned int, char, ...
[浮動小数点型] float, double, ...
[ポインタ型]
[ポインタ] int const*, int (*)(void), ...
[メンバポインタ] int T::*, int (T::*)(void), ...
[列挙型] enum
特定の条件を満たす
ユーザー定義クラス
void (C++14 以降)
19. ◆C++11 constexpr 導入の歴史的経緯
• constexpr 導入の動機 1
– 定数を返す関数をコンパイル時評価可能にす
るため
// レガシーなマクロ定数
static_assert(2147483647L <= INT_MAX, “”);
// constexpr が無ければこれはできない
static_assert(2147483647L <= std::numeric_limits<int>::max(), “”);
20. ◆C++11 constexpr 導入の歴史的経緯
• constexpr 導入の動機 2
– テンプレートメタプログラミングでやってい
たコンパイル時計算を自然な関数で書けるよ
うにするため
// TMP で (16^2)^2
typedef Square<Square<int_<16> >::type>::type Result;
static_assert(Result::value == 65536, “”);
// constexpr で (16^2)^2
constexpr auto result = square(square(16));
static_assert(result == 65536, “”);
21. ◆C++11 constexpr 導入の歴史的経緯
• C++11 constexpr 関数のつらい点
– ローカル変数宣言ができない
– あらゆる副作用が許されない
• (もちろん変数書き換えもダメ)
– ループ文や if, switch 等の構文が使えない
• (関数の再帰はできる)
– 処理を実質 return 文ひとつで記述しなけれ
ばならない
22. ◆C++11 constexpr 導入の歴史的経緯
• 最初は関数の再帰さえ許可されない予定
だった
– 再帰ダメだよ派
• コンパイラの実装が面倒になる
• 絶対濫用するヤツが出てくる
– 再帰いいでしょ派
• たいした処理が書けなくなる
• TMP によるコンパイル時計算の代替にならない
26. ◆C++14 constexpr の制限緩和
• C++14 constexpr の制限緩和
– ローカル変数宣言の許可
– 変数書き換えの許可
– ループ文や if, switch 等の構文の許可
– 文をいくらでも記述できるようになった
28. ◆C++14 constexpr の制限緩和
• find アルゴリズム実装例(非constexpr)
template<typename Iter, typename T>
Iter find(Iter first, Iter last, T const& value) {
while (first != last) {
if (*first == value) return first;
++first;
}
return last;
}
29. ◆C++14 constexpr の制限緩和
• find アルゴリズム実装例(C++11 constexpr)
template<typename Iter, typename T>
constexpr Iter find_impl(
Iter first, Iter last, T const& value,
typename iterator_traits<Iter>::difference_type pivot, Iter found)
{
return found != first ? found
: pivot == 0 ? (*first == value ? first : last)
: find_impl(
next(first, pivot), last, value,
(distance(first, last) - pivot) / 2,
find_impl(
first, next(first, pivot), value,
pivot / 2, first)
);
template<typename Iter, typename T>
constexpr Iter find(Iter first, Iter last, T const& value) {
return first == last ? last
: find_impl(first, last, value, distance(first, last) / 2, first);
}
30. ◆C++14 constexpr の制限緩和
• find アルゴリズム実装例(C++11 constexpr)
template<typename Iter, typename T>
constexpr Iter find_impl(
Iter first, Iter last, T const& value,
typename iterator_traits<Iter>::difference_type pivot, Iter found)
{
return found != first ? found
: pivot == 0 ? (*first == value ? first : last)
: find_impl(
next(first, pivot), last, value,
(distance(first, last) - pivot) / 2,
find_impl(
first, next(first, pivot), value,
pivot / 2, first)
);
template<typename Iter, typename T>
constexpr Iter find(Iter first, Iter last, T const& value) {
return first == last ? last
: find_impl(first, last, value, distance(first, last) / 2, first);
}
再帰深度オーダーを抑える工
夫が必要
ループも if もインクリメント
も書けない
実装用関数を分けたり
色々とつらい
32. a3 a4 a5 a6 a7a2
◆再帰深度のオーダー
• 再帰深度のオーダー(二分再帰の場合)
a0 a1各項
1 1 1 1
2 2
3再帰深度
オーダー:
比較回数 = Ο(N)
再帰深度 = Ο(logN)
+ + + +
+ +
+
33. ◆C++14 constexpr の制限緩和
• find アルゴリズム実装例(C++14 constexpr)
template<typename Iter, typename T>
constexpr Iter find(Iter first, Iter last, T const& value) {
while (first != last) {
if (*first == value) return first;
++first;
}
return last;
}
34. ◆C++14 constexpr の制限緩和
• find アルゴリズム実装例(C++14 constexpr)
template<typename Iter, typename T>
constexpr Iter find(Iter first, Iter last, T const& value) {
while (first != last) {
if (*first == value) return first;
++first;
}
return last;
}
宣言に constexpr 指定を追加
しただけ
実際明快でわかりやすい
35. ◆C++14 constexpr の制限緩和
• merge アルゴリズム実装例(非constexpr)
template<typename Iter1, typename Iter2, typename OutIter>
OutIter
merge(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2, OutIter result) {
for (; ; ) {
if (first1 == last1) {
return copy(first2, last2, result);
}
if (first2 == last2) {
return copy(first1, last1, result);
}
*result++ = *first2 < *first1
? *first2++ : *first1++;
}
}
37. ◆C++14 constexpr の制限緩和
• merge アルゴリズム実装例(C++14
constexpr)
template<typename Iter1, typename Iter2, typename OutIter>
constexpr OutIter
merge(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2, OutIter result) {
for (; ; ) {
if (first1 == last1) {
return copy(first2, last2, result);
}
if (first2 == last2) {
return copy(first1, last1, result);
}
*result++ = *first2 < *first1
? *first2++ : *first1++;
}
}
38. ◆C++14 constexpr の制限緩和
• C++14 constexpr であっても許可され
ない制限(例)
– I/O
– 動的メモリ(new/delete)
– 例外処理(try-catch)
– RAII(デストラクタを使った後処理)
– ラムダ式
– グローバル変数の参照
39. ◆C++14 constexpr の制限緩和
• 抽象マシンモデルの採用
• C++11 constexpr 関数の評価は、関数呼び出
しの置換(function invocation substitution)と
いう規則で定義されていた
– 関数の評価を、呼び出し先の関数の式の評価と置換
することで、関数呼び出しをエミュレートする
– 式変形の最適化に近い
• C++14 constexpr では、定数式の評価はC++
抽象マシンのサブセットとして再定義される
40. ◆処理系の constexpr 対応状況
• C++11 constexpr 対応
– GCC 4.7.0 以降
– Clang 3.2 以降
• C++14 constexpr 対応
– Clang 3.3 以降
41. ◆処理系の constexpr 対応状況
• GCC
– C++11 constexpr にはよく対応している
– C++14 constexpr 対応はまだ
– 数学関数など有用な組み込み関数あり
– メモ化のおかげで⾼速だがメモリ⾺⿅⾷い
42. ◆処理系の constexpr 対応状況
• Clang
– C++11 constexpr に対応
– 唯一 C++14 constexpr 対応が進んでいる
– constexpr 関係の大きなバグが残っている
• 相互再帰する constexpr 関数がコンパイルエラー
43. ◆処理系の constexpr 対応状況
• ICC (Intel C++ Compiler)
– ICC 11 で C++11 constexpr を使える
• が、バグが多くて constexpr 実用はまだ難しい
• 今後に期待する
44. ◆処理系の constexpr 対応状況
• VC++ (Microsoft Visual C++)
– November 2013 CTPで C++11 constexpr
を使える
• と発表されたが、constexpr メンバ関数に未対応
なので複雑な処理はまったく書けない
• 今後に期待できるのだろうか?
48. ◆C++ らしいライブラリ設計とは
• Boost ライブラリの特徴(独断)
– とにかくテンプレート
• 静的ダックタイピング
• 機能の非メンバ関数化
• 疎結合な機能群
– 機能分割がしっかりしている
– 『C++ らしい』と思う
• STLから以降の標準ライブラリの流れでもある
51. ◆ライブラリを constexpr 化しよう
• ポリモーフィズムの例(仮想関数版)
– 関数 foo を constexpr 化するには?
struct Base {
virtual int f() const = 0;
};
int foo(Base const& t) {
return t.f();
}
52. ◆ライブラリを constexpr 化しよう
• ポリモーフィズムの例(テンプレート版)
– 動的ポリモーフィズムから静的ポリモーフィズムへ
template<typename T>
constexpr auto foo(T const& t)
-> decltype(t.f()) {
return t.f();
}
constexpr で仮想関数は使え
ないのでテンプレートにする
53. ◆ライブラリを constexpr 化しよう
• コンテナ操作の例(コンテナ決め打ち版)
– 関数 remove_erase を constexpr 化するには?
template<typename T>
void remove_erase(vector<T>& c, T const& val) {
c.erase(remove(begin(c), end(c), val), end(c));
}
vector<int> c = { 1, 1, 2, 2, 3, 3 };
remove_erase(c, 1);
54. ◆ライブラリを constexpr 化しよう
• コンテナ操作の例(テンプレート版)
– 型の決め打ちを排除する
template<typename Container, typename T>
constexpr void remove_erase(Container& c, T const& val) {
c.erase(remove(begin(c), end(c), val), end(c));
}
deque<int> c = { 1, 1, 2, 2, 3, 3 };
remove_erase(c, 1);
std::vector は非リテラル型な
のでテンプレートにする
vector 以外の
コンテナでも使える
55. ◆ライブラリを constexpr 化しよう
• コンテナ操作の例(Rangeアダプタ版)
–Rangeアダプタ版ならば C++11
constexpr でも使える
deque<int> c = { 1, 1, 2, 2, 3, 3 };
auto c2 = c | sprout::adaptors::removed(1);
Rangeアダプタは
元のコンテナを変更しない
「処理後の範囲」が欲しいだけ
ならこれでOK
56. ◆型制約について
• Rangeアダプタとは?
– Range はイテレータの組
• begin(Rng) と end(Rng) で始端と終端を取得
= 型制約
– Rng | Adaptor が Range を返すものが
Rangeアダプタ
• Rng | reversed
• Rng | sorted など
62. ◆非メンバ関数のススメ
• 機能はメンバ関数よりも非メンバ関数と
して追い出したほうがよい
– Rng.size() よりも size(Rng)
– より小さな型制約で済む
– クラスの肥大化を避けられる
– より疎結合な設計になる
template<typename Range>
constexpr auto size(Range const& rng)
-> decltype(distance(begin(rng), end(rng))) {
return distance(begin(rng), end(rng));
}
非メンバ関数の size(Rng) は
begin と end だけ
あれば実装できる
64. ◆ライブラリを constexpr 化しよう
• ポリモーフィズムの例(仮想関数版)
– 関数 foo を constexpr 化するには?
struct Base {
virtual int f() const = 0;
};
int foo(Base const& t) {
return t.f();
}
70. ◆Sprout.Darkroom の機能階層
• 大概のデータはタプルの組合せで表
現できる
– ベクトル { x, y, z }
– 色 { R, G, B }
– 光線 { 始点ベクトル, 方向ベクトル }
– 材質 { 色, 反射率, 透過率, ... }
– 衝突情報 { 距離, 位置ベクトル, 法線
ベクトル, ... }
72. ◆光源の定義
• 例: point_light 光源のインタフェース
template<typename Position, typename Color>
class basic_point_light {
public:
constexpr basic_point_light(position_type const& pos, color_type const&
col);
template<typename Intersection, typename Objects>
constexpr color_type
operator() (Intersection const& inter, Objects const& objs) const;
};
73. ◆光源の定義
• 例: point_light 光源のインタフェース
template<typename Position, typename Color>
class basic_point_light {
public:
constexpr basic_point_light(position_type const& pos, color_type const&
col);
template<typename Intersection, typename Objects>
constexpr color_type
operator() (Intersection const& inter, Objects const& objs) const;
};
operator() メンバ関数
第 2 引数のオブジェクトは
遮蔽判定のために使われる
位置、輝度
の情報を保持する
76. ◆Sprout.Weed
• コンパイル時パーサコンビネータ
constexpr auto unbracket_uuid_p
= repeat[lim<16>(hex8f)]
| repeat[lim<4>(hex8f)]
>> '-' >> repeat[lim<2>(hex8f)] >> '-' >> repeat[lim<2>(hex8f)]
>> '-' >> repeat[lim<2>(hex8f)] >> '-' >> repeat[lim<6>(hex8f)];
constexpr auto uuid_p
= (unbracket_uuid_p | '{' >> unbracket_uuid_p >> '}') >> eoi;
constexpr auto parsed = parse_range(
to_string( "{550E8400-E29B-41D4-A716-446655440000}“ ),
uuid_p
);
77. ◆Sprout.Weed
• コンパイル時パーサコンビネータ
constexpr auto unbracket_uuid_p
= repeat[lim<16>(hex8f)]
| repeat[lim<4>(hex8f)]
>> '-' >> repeat[lim<2>(hex8f)] >> '-' >> repeat[lim<2>(hex8f)]
>> '-' >> repeat[lim<2>(hex8f)] >> '-' >> repeat[lim<6>(hex8f)];
constexpr auto uuid_p
= (unbracket_uuid_p | '{' >> unbracket_uuid_p >> '}') >> eoi;
constexpr auto parsed = parse_range(
to_string( "{550E8400-E29B-41D4-A716-446655440000}“ ),
uuid_p
);
Expression Template
78. ◆ Sprout.Weed
• 典型的なExpression Template (ET)
–大体 Boost.Spirit (v2) に倣っている
•ET のスケルトンは相当カジュアルに書き
直している
–ET は一時変数の使用を抑制するために
発明された
• C++11 constexpr と相性バツグン
84. ◆Sprout.Compost の機能階層
• 基本波形
– blanked (空白)
– sinusoidal (正弦波)
– sawtooth_wave (ノコギリ波)
– square_wave (矩形波)
– triangle_wave (三角波)
– white_noise (ホワイトノイズ)
86. ◆Sprout.Compost の機能階層
• sinusoidal (正弦波)
–sinusoidal(freq, amp, phase)
-> sinusoid_range を返す
– sinusoid_iterator の組
– インデックス i に対して
• amp * sin(2 * pi * freq * i + phase)
90. ◆Sprout.Compost の機能階層
• distorted (ディストーション)
–Rng | disorted(gain, level)
-> Rng
| changed_volume(gain)
| clipped()
| changed_volume(level)
振幅を上げる
100%を越えた部分
をクリップする
レベル調節
93. ◆Sprout.Compost の機能階層
• Range アダプタのデメリット
–エフェクトをふたつ重ねてみる
–生成されるイテレータの型
decltype(begin( sinusoidal(1.) | distorted(2., .5) | reverbed(1., 1.) ))
sprout::transform_iterator<sprout::compost::reverb_outdirected_value<doubl
e, int>,
sprout::counting_iterator<sprout::indexed_iterator<sprout::transform_iterator
<sprout::binder2nd<sprout::multiplies<void>, double>,
sprout::clamp_iterator<sprout::transform_iterator<sprout::binder2nd<sprout::
multiplies<void>, double>, sprout::sinusoid_iterator<double>, void>,
sprout::less<double> >, void> > >, void>
94. ◆Sprout.Compost の機能階層
• Range アダプタのデメリット
–エフェクトをふたつ重ねてみる
–生成されるイテレータの型
decltype(begin( sinusoidal(1.) | distorted(2., .5) | reverbed(1., 1.) ))
sprout::transform_iterator<sprout::compost::reverb_outdirected_value<doubl
e, int>,
sprout::counting_iterator<sprout::indexed_iterator<sprout::transform_iterator
<sprout::binder2nd<sprout::multiplies<void>, double>,
sprout::clamp_iterator<sprout::transform_iterator<sprout::binder2nd<sprout::
multiplies<void>, double>, sprout::sinusoid_iterator<double>, void>,
sprout::less<double> >, void> > >, void>
とても明快でわかりやすい
99. ◆標準ライブラリの constexpr 対応状況
• std::array::operator[] の例
reference
operator[](size_type i) {
return elems[ i ] ;
}
constexpr const_reference
operator[](size_type i) const {
return elems[ i ] ;
}
100. ◆標準ライブラリの constexpr 対応状況
• std::array::operator[] の例
reference
operator[](size_type i) {
return elems[ i ] ;
}
constexpr const_reference
operator[](size_type i) const {
return elems[ i ] ;
}
非const版
いまだ constexpr 指定されない
const版
新たに constexpr 指定される
102. ◆C++1z constexpr 化の検討
• C++1z に向けて constexpr 対応で
きそうな機能を検討してみた
– gist.github.com/bolero-MURAKAMI/9283758
– 約350 の constexpr 化可能な関数を
ピックアップ
–うち 約200 が直ちに constexpr 指定
できる(すべきである)
•もちろん個別具体的な議論が必要
103. ◆C++1z constexpr 化の検討
• 直ちに constexpr 指定できる例
–リテラル型のコピー/ムーブ代入演算⼦
•tuple, complex など
–小さなユーティリティ関数
•swap, rel_ops など
–getterの非const版オーバーロード
•array::front, array::at など
105. ◆まとめ
• constexpr は C++11 での導入から
C++14 へと進化を続けている
• constexpr の制限を指針にすること
で C++ らしいジェネリックな設計
を講じることができる
• C++1z へ向けて標準ライブラリの整
備を進めるべき