SlideShare ist ein Scribd-Unternehmen logo
1 von 138
Downloaden Sie, um offline zu lesen
constexpr中3女子テクニック
―実践と濫⽤そしてC++14へ
Boost.勉強会 #12
bolero_MURAKAMI
2013/6/22
◆自己紹介
• 名前 : 村上 原野 (むらかみ げんや)
@bolero_MURAKAMI, id:boleros
• 棲息地: 大都会岡山
• 仕事 : 猪風来美術館陶芸指導員
・普段はろくろをまわしたり、
縄文土器をつくったりしています
・趣味は constexpr です
◆自己紹介
• 公開しているライブラリ:
Sprout C++ Library (constexpr ライブラリ)
github.com/bolero-MURAKAMI/Sprout
• 前回発表資料:
Boost.勉強会 #7
【中3⼥⼦でもわかる constexpr】
Boost.勉強会 #8
【中3⼥⼦が狂える本当に気持ちのいい constexpr】
www.slideshare.net/GenyaMurakami
◆アジェンダ
• はじめに
• いまさら聞けない constexpr 入門
• 逆引き constexpr マニュアル
• constexpr アルゴリズム実装テクニック
• これからの constexpr の話
• まとめ
◆いまさら聞けない constexpr 入門
• constexpr とは
• constexpr を使うべき 5 の理由
• constexpr の落とし⽳
◆C++ プログラミングのレイヤー
プリプロセス時の世界
(魔界)
コンパイル時の世界
(ライブラリアンが多数棲息)
実⾏時の世界
(人間界)
[プログラマのすること][処理されるもの]
ソースコード
プリプロセッサ
メタプログラミング
テンプレート
メタプログラミング
型
実⾏時オブジェクト
定数式
通常の
プログラミング
C++03
◆C++ プログラミングのレイヤー
プリプロセス時の世界
(魔界)
コンパイル時の世界
(ライブラリアンが多数棲息)
実⾏時の世界
(人間界)
[プログラマのすること][処理されるもの]
constexpr
ソースコード
プリプロセッサ
メタプログラミング
テンプレート
メタプログラミング
型
実⾏時オブジェクト
定数式
通常の
プログラミング
C++11
◆constexpr で扱えるデータ
[リテラル型]
[スカラ型] [リテラル型の配列]
LiteralType [N]
[リテラル型への参照]
LiteralType const&
[算術型]
[整数型] int, unsigned int, char, ...
[浮動小数点型] float, double, ...
[ポインタ型]
[ポインタ] int const*, int (*)(void), ...
[メンバポインタ] int T::*, int (T::*)(void), ...
[列挙型] enum
特定の条件を満たす
ユーザ定義クラス
◆constexpr を使うべき 5 の理由
• 明示的なコンパイル時定数の定義
• コンパイル時定数を返す関数
• 副作⽤がないことを保証する
• better TMP
• 初期化をあらかじめ⾏っておく
◆明示的なコンパイル時定数の定義
• 定数だがコンパイル時定数ではない例
– ill-formed !!!
struct X { int n; };
const X x = { 10 };
int a[x.n] = { 1 };
配列の宣⾔にはコンパイル時定数が必要
◆明示的なコンパイル時定数の定義
• constexpr で、実⾏時定数ではなくコンパイル時定数で
あることを明示する
struct X { int n; };
constexpr X x = { 10 };
int a[x.n] = { 1 };
OK! コンパイル時定数
◆コンパイル時定数を返す関数
• コンパイル時に定まる値だがコンパイル時定数として使
えない例
template<class T, size_t N>
struct MyArray {
T elem[N];
size_t size() const { return N; }
};
N はコンパイル時定数だが、
size() はコンパイル時定数でない
◆コンパイル時定数を返す関数
• constexpr で、関数をコンパイル時定数として使えるよ
うにする
template<class T, size_t N>
struct MyArray {
T elem[N];
constexpr size_t size() const { return N; }
};
OK! コンパイル時定数
◆副作⽤がないことを保証する
• 副作⽤があるか無いか分からない例
int x = format();
C:ドライブをフォーマットする副作⽤がある
かもしれない
◆副作⽤がないことを保証する
• 副作⽤が確実にない例
constexpr int x = format();
C:ドライブをフォーマットするとしても、
その前にコンパイルエラーになる
◆better TMP
• 煩雑なテンプレートメタプログラミング
typedef boost::mpl::vector_c<int, 1, 2, 3, 4, 5> src;
typedef typename boost::mpl::accumulate<
src,
boost::mpl::int_<0>,
boost::mpl::plus<boost::mpl::_1, boost::mpl::_2>
>::type accumulated;
◆better TMP
• ⾒慣れたプログラムと変わらない constexpr
constexpr auto src = make_array<int>( 1, 2, 3, 4, 5 );
constexpr auto accumulated =
range::accumulate(src, 0, plus<>() );
◆初期化をあらかじめ⾏っておく
• コンパイル時三角関数テーブル
using namespace sprout::adaptors;
constexpr array<double, 90> degree_sin_table
= sinusoidal( 1. / 360 ) | copied;
浮動小数点数のコンパイル時計算が出来るの
は constexpr だけ
◆初期化をあらかじめ⾏っておく
• constant initialization
– あらゆる動的初期化より先に⾏われるため、ありがちな静的変
数の初期化順序への依存や競合が起こらない
static std::mutex m; // 普通のグローバル変数
constexpr コンストラクタを持つ型
◆constexpr の落とし⽳
• 定数式だけど定数じゃない
• 実は副作⽤を禁止できない
◆定数式だけど定数じゃない
• すごく const っぽいシグネチャ
constexpr int const& f( int const& t );
◆定数式だけど定数じゃない
• すごく const っぽくないシグネチャにしてみる
constexpr int& f( int& t );
完全に合法
◆定数式だけど定数じゃない
• const_cast で const 外しをしてみる
constexpr int& f( int const& t ) {
return const_cast<int&>(t);
}
完全に合法
◆定数式だけど定数じゃない
• constexpr 指定の変数が暗黙の const 修飾されるだけ
で、constexpr 関数の引数や返値の const 性とは何も
関わりがない
• たとえコンパイル時の定数式評価であっても、非 const
rvalue/lvalue 参照などの、あらゆる value category
の式が扱われる
◆実は副作⽤を禁止できない
• 人生、宇宙、すべての答えを代入するだけの関数
template<class T>
T& f( T&& t ) { return t = 42; }
もちろん副作⽤がある
◆実は副作⽤を禁止できない
• これもそのまま constexpr 関数に出来る
template<class T>
constexpr T& f( T&& t ) { return t = 42; }
◆実は副作⽤を禁止できない
• これもそのまま constexpr 関数に出来る
template<class T>
constexpr T& f( T&& t ) { return t = 42; }
constexpr int k = f(0);
もちろんコンパイル時に評価しよう
とするとエラーになるが……
int i = 0;
int j = f( i );
実⾏時評価だと何も問題なく
コンパイル・実⾏できる
◆実は副作⽤を禁止できない
• 引数によって副作⽤があったり無かったりする場合
template<class T>
constexpr T& f( T&& t, bool cond ) { return cond ? t : (t = 42); }
constexpr int k = f( 0, true ); /* 副作用なし */
int i = 0;
int j = f( i, false ); /* 副作用あり */
◆実は副作⽤を禁止できない
• constexpr 関数が副作⽤について保証するのは以下の場
合しかない
– コンパイル時に呼び出されたとき、副作⽤があればコンパイル
エラーになる
– コンパイル時に呼び出せる(副作⽤がない)とき、実⾏時にそれ
と等値な引数で呼び出しても、同じく副作⽤がない
• 実⾏時の constexpr 関数呼び出しで、副作⽤がないこ
とを証明するには以下の場合しかない
– コンパイル時に等値な引数で呼び出して、エラーにならないこ
とを確認する
– 実装を⾒て、全ての実⾏パスで副作⽤が起こりえないことを検
証する
◆実は副作⽤を禁止できない
• constexpr 関数はコンパイル時にも実⾏時にも呼び出せ
るが、実⾏時に副作⽤がないことはまったく保証しない
• constexpr 指定の有無が、ドキュメントレベルの目安に
はなる
◆逆引き constexpr マニュアル
• constexpr で文字列を扱いたい
• constexpr で配列(コンテナ)を扱いたい
• constexpr でタプルを扱いたい
• constexpr でアルゴリズムを使いたい
• constexpr で RangeAdaptor を使いたい
• constexpr でアサーションを使いたい
• constexpr で数学関数を使いたい
• constexpr で乱数を使いたい
• constexpr でハッシュ関数を使いたい
• constexpr で UUID を使いたい
• constexpr で構文解析したい
• constexpr でレイトレーシングしたい
• constexpr で波形編集したい
◆文字列を扱いたい
• それ Sprout.String で出来るよ!
• リテラルから文字列クラスへ
• 文字列連結
• std::string 互換のインタフェース
#include <sprout/string.hpp>
constexpr auto s = to_string( "Hello world!" );
// -> string<12> :型には要素数(最大文字数)が含まれる
constexpr auto s = to_string( "Hello" ) + to_string( "world!" );
// string<5> + string<7> -> string<12>
constexpr auto pos = s.find( "o wo" ); // 4
constexpr auto s2 = s.substr( 0, pos ); // "Hell"
◆文字列を扱いたい
• 文字列→算術型の変換
• 算術型→文字列の変換
• その他、辞書順⽐較やストリーム入出⼒ etc...
constexpr auto istr = to_string( 37564 ); // "37564"
constexpr auto dstr = to_string( 3.141592 ); // "3.141592"
constexpr auto i = stoi( to_string( "37564" ) ); // 37564
constexpr auto d = stod( to_string( "3.141592" ) ); // 3.141592
◆文字列を扱いたい
• 所有権を持たない文字列クラス
– (C++1y の std::string_view 互換)
#include <sprout/utility/string_view.hpp>
constexpr auto s = string_view( "Hello world!" );
// -> string_view :型には要素数を含まない
◆配列(コンテナ)を扱いたい
• それ Sprout.Array で出来るよ!
• 配列の作成
• std::array 互換のインタフェース
• constexpr なイテレータ
#include <sprout/array.hpp>
constexpr auto a = make_array<int>( 1, 2, 3, 4, 5 );
// -> array<int, 5>
constexpr auto auto size = a.size(); // 5
constexpr auto i2 = a[2]; // 3
constexpr auto i5 = a.at(5); // error: out_of_range
constexpr auto first = a.begin();
constexpr auto last = a.end();
◆タプルを扱いたい
• それ Sprout.Tuple で出来るよ!
• タプルの作成
• std::tuple 互換のインタフェース
• ファンクタを呼ぶ
#include <sprout/tuple.hpp>
constexpr auto t = make_tuple( 10, 3.14, to_string( "Foo" ) );
// -> tuple<int, double, string<3>>
constexpr auto t = make_tuple( 10, 3.14 );
constexpr auto f = make_fused( plus<>() );
constexpr auto result = f(t); // call: 10 + 3.14
constexpr auto size = tuple_size<decltype(t)>::value; // 3
constexpr auto i1 = get<1>(t); // 3.14
◆アルゴリズムを使いたい
• それ Sprout.Algorithm で出来るよ!
• 変更を伴わないアルゴリズム
– (STL アルゴリズム互換)
#include <sprout/algorithm.hpp>
constexpr auto a = make_array<int>( 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 );
static_assert(
is_sorted( a.begin(), a.end() ),
"ソートされているか" );
static_assert(
all_of( a.begin(), a.end(), bind2nd( modulus<>(), 2 ) ),
"奇数であるか" );
◆アルゴリズムを使いたい
• 変更を伴うアルゴリズム
• 変更を伴うアルゴリズムの STL との違い
– 出⼒イテレータを取るアルゴリズムは、代わりに出⼒コンテナ
を受け取って結果を返す
– 入出⼒イテレータのペアを取るアルゴリズムは、代わりに入出
⼒コンテナを受け取って結果を返す
constexpr auto a = make_array<int>( 5, 1, 9, 4, 8, 2, 7, 3, 10, 6 );
constexpr auto sorted = sort( a ); // 1 2 3 4 5 6 7 8 9 10
constexpr auto reversed = reverse( sorted ); // 10 9 8 7 6 5 4 3 2 1
std::reverse_copy( first, last, out ); // ↓
constexpr auto reversed = reverse_copy( first, last, container );
std::reverse( first, last ); // ↓
constexpr auto reversed = reverse( container );
◆アルゴリズムを使いたい
• Range 版アルゴリズムもあるよ!
#include <sprout/range/algorithm.hpp>
constexpr auto a = make_array<int>( 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 );
static_assert(
is_sorted( a ),
"ソートされているか" );
static_assert(
all_of( a, bind2nd( modulus<>(), 2 ) ),
"奇数であるか" );
◆RangeAdaptor を使いたい
• それ Sprout.Range.Adaptor で出来るよ!
• 様々なアダプタをパイプ演算⼦で繋げる
• Haskell で書くと
#include <sprout/range/adaptor.hpp>
using namespace sprout::adaptor;
constexpr array<int, 10> a =
counting( 1 ) // [1..] の無限リスト
| transformed( bind2nd( multiplies<>(), 2 ) ) // 全要素に (* 2) 適用
| taken( 5 ) // 5 要素を取り出し
| jointed( adaptor::counting( 11 ) ) // [11..] をリスト連結
| copied // 任意のコンテナへ変換可能にする
;
// 2 4 6 8 10 11 12 13 14 15
(++ [11..]) $ take 5 $ map (* 2) [1..]
◆RangeAdaptor を使いたい
• RangeAdaptor の特⻑
– 遅延評価が出来る
– 一時オブジェクトの生成を最小限に出来る
– アダプタの適⽤が副作⽤を持たない
– constexpr に向いている!
◆アサーションを使いたい
• それ Sprout.Assert で出来るよ!
• コンパイル時でも実⾏時でも使えるアサート
• コンパイル時の場合→コンパイルエラー (GCC)
• 実⾏時の場合→標準の assert と同じ (GCC)
#include <sprout/assert.hpp>
template<typename T>
constexpr T div(T num, T denom) {
return SPROUT_ASSERT(denom != 0), (num / denom);
}
div(3.14, 0.0);
◆アサーションを使いたい
• コンパイル時の場合→コンパイルエラー (GCC)
• 実⾏時の場合→標準の assert と同じ (GCC)
in constexpr expansion of ‘sprout::detail::assertion_check((denom != 0.0),
((const char*)"***** Internal Program Error - assertion (denom != 0) failed:
a.cpp(6)"))’
***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6)
◆数学関数を使いたい
• それ Sprout.Math で出来るよ!
• <cmath> の殆どの数学関数をサポート
• GCC のビルトイン関数が使える環境ならより高速に
#include <sprout/math.hpp>
constexpr auto v1 = cos(0.5); // 三角関数
constexpr auto v2 = tgamma(3.0); // ガンマ関数
◆乱数を使いたい
• それ Sprout.Random で出来るよ!
• 乱数種
• いくつかの疑似乱数生成エンジン
• いくつかの分布クラス
#include <sprout/random.hpp>
constexpr auto rng1 = minstd_rand0( seed ); // 線形合同法エンジン
constexpr auto rng2 = taus88( seed ); // 結合トーズワース法エンジン
constexpr std::size_t seed = SPROUT_UNIQUE_SEED;
// コンパイル日時、ファイル名、行数を利用
constexpr auto dist1 = uniform_int_distribution<>( 1, 6 ); // 整数一様分布
constexpr auto dist2 = normal_distribution<>( 5, 2 ); // 正規分布
◆乱数を使いたい
• 返値は { 生成値, 次の状態の生成器 } のペアになる
• 乱数列を ForwardTraversalRange として扱う
constexpr auto rnd = dist(rng); // 乱数生成
constexpr auto val = rnd.generated_value(); // 生成値
constexpr auto gen = rnd.next_generator(); // 次の生成器
constexpr auto seq = random::make_range( rng, dist ); // 乱数列
constexpr array<int, 10> a = seq | adaptor::copied;
◆ハッシュ関数を使いたい
• それ Sprout.Functional.Hash で出来るよ!
• std::hash 互換のインタフェース
#include <sprout/functional/hash.hpp>
constexpr std::size_t h = hash<double>( 3.14 );
◆ハッシュ関数を使いたい
• Sprout.Checksum でハッシュアルゴリズムも使える
よ!
• MD5 や SHA1 のハッシュ関数
• メソッドチェインでソースを流し込む
#include <sprout/checksum.hpp>
constexpr auto md5_hash = md5().process_range( to_string( "foobar" ) )();
constexpr auto sha1_hash = sha1().process_range( to_string( "foobar" ) )();
◆UUID を使いたい
• それ Sprout.Uuid で出来るよ!
• boost::uuids::uuid 互換のインタフェース
#include <sprout/uuid.hpp>
◆UUID を使いたい
• 文字列から UUID 生成
• ランダムな UUIDv4 生成
• MD5/SHA1 による UUIDv3/v5 生成
constexpr auto id = uuids::make_uuid(
to_string( "{550e8400-e29b-41d4-a716-446655440000}" ) );
// 文字列と同じ UUID
constexpr auto id = uuids::make_uuid4( SPROUT_UNIQUE_SEED );
// 任意の乱数種または乱数生成器による UUIDv4
constexpr auto id = uuids::make_uuid5_dns( to_string( "boost.org" ) );
// DNS 名前空間の SHA1 による UUIDv5
◆UUID を使いたい
• UUID ユーザ定義リテラル
constexpr auto id3 = "{550e8400-e29b-41d4-a716-446655440000}"_uuid;
constexpr auto id5 = "DNS"_uuid5( to_string( "boost.org" ) );
◆構文解析したい
• そうだね、Sprout.Weed だね!!
• 例)C++の識別⼦(予約済み識別⼦を除く)にマッチする
パーザを作成する
#include <sprout/weed.hpp>
constexpr auto identifier_p =
!( '_' >> char_( "A-Z“ ) ) // アンダースコア+大文字で始まらない
>> char_( "a-zA-Z_“ ) // 英字またはアンダースコアで始まる
>> *lim<15>( char_( "0-9a-zA-Z_“ ) - "__“ )
// ゼロ個以上の英数字またはアンダースコア(アンダースコアは連続しない)
;
◆構文解析したい
• パーザの適⽤
constexpr auto s1 = to_string( "_foobar" );
static_assert( parse_range( s1, identifier_p ).current() == s1.end(),
"マッチする" );
constexpr auto s2 = to_string( "_Foobar" );
static_assert( parse_range( s2, identifier_p ).current() != s2.end(),
"マッチしない" );
constexpr auto s3 = to_string( "foo__bar" );
static_assert( parse_range( s3, identifier_p ).current() != s3.end(),
"マッチしない" );
◆レイトレーシングしたい
• 中3⼥⼦のマストアイテム Sprout.Darkroom
#include <sprout/darkroom.hpp>
◆レイトレーシングしたい
• 鏡面や複数光源の設定
◆レイトレーシングしたい
• 合わせ鏡のような再帰的追跡
◆レイトレーシングしたい
• テクスチャファイルの読み込み・貼り付け
◆波形編集したい
• ナウなヤングにバカウケ Sprout.Compost
• 単純な正弦波の生成
#include <sprout/compost.hpp>
using namespace sprout::compost;
constexpr array<complex<double>, 256> src =
waves::sinusoidal( 10. / 256, 10000. ) | ranges::copied;
◆波形編集したい
• DFT (離散フーリエ変換)
constexpr auto trans = src | analyses::dft | ranges::copied;
// 離散フーリエ変換
constexpr auto spec =
trans | analyses::amplitude_spectrum | ranges::copied;
// 振幅スペクトルに変換
◆波形編集したい
• 単音の正弦波を生成
– 再生:sine_sound.wav
using namespace sprout::compost;
constexpr array<std::int16_t, size> wav =
waves::sinusoidal( // 指定音階の正弦波を生成
equal_temperament_value( semitones ) * base / sample_per_sec, 0.8 )
| formats::as_pcm_wave16 // 16bitWAVE に変換
| ranges::copied // 任意のコンテナに変換可能にする
;
◆波形編集したい
• モーツァルトのきらきら星変奏曲ハ⻑調K. 265の最初の
部分のメロディを生成
– 再生:sine_twinkle.wav
◆波形編集したい
• 外部ファイルから波形データの読み込み
– 再生:sound.wav
#define COMPOST_DEF_LOAD_SOURCE_IDENTIFIER wav
#define COMPOST_DEF_LOAD_INFO_IDENTIFIER wav_info
#define COMPOST_DEF_LOAD_SOURCE_FILE FILENAME
#include COMPOST_LOAD_SOURCE
◆波形編集したい
• ディストーションをかける
– 再生:distortion.wav
constexpr sprout::array<std::int16_t, size> wav_data =
wav.elements()
| effects::distorted( 100., 0.5 ) // ディストーション
| formats::as_pcm_wave16 // 16bitWAVE に変換
| ranges::copied // 任意のコンテナに変換可能にする
;
◆波形編集したい
• 音声合成したい
– Rosenberg 波を音源波形とする(τ1 = 0.8, τ2 = 0.16)
– フォルマント(音声のスペクトルに固有のピーク)を⽤意する
– IIR フィルタ(無限インパルス応答において特定の周波数成分を
取り出す)でレゾナンスを掛ける
– 再生:vowel.wav
◆constexpr アルゴリズム実装テクニック
• 再帰深度を抑える
• アルゴリズム
• クラス設計
• その他
◆再帰深度を抑える
• 再帰深度を抑える意義
• IndexTuple イディオム
• 倍分再帰
◆再帰深度を抑える意義
• constexpr 関数においてループは再帰で実装できるが、
その深度は実装によって制限される
• 規格が実装に推奨する constexpr 関数の再帰深度 512
– 数千要素の配列を、線形再帰でループするとすぐ制限を超える
– ちなみにテンプレートインスタンス化の再帰深度 1024
◆再帰深度を抑える意義
• 重要なのは計算量(オーダー)
– 線形オーダー Ο(n) だと厳しい
– 対数オーダー Ο(log n) だとかなり良い(例えば要素数 16→256
でも再帰数 4→8 )
– 定数オーダー Ο(1) なら最高(要素数が増えても変わらない)
◆IndexTuple イディオム
• IndexTuple イディオムで reverse の実装
template<class T, size_t N, ptrdiff_t... Indexes>
constexpr array<T, N>
reverse_impl( array<T, N> const& arr, index_tuple<Indexes...> ) {
return array<T, N>{{ arr[N-1-Indexes]... }};
}
template<class T, size_t N>
constexpr array<T, N> reverse( array<T, N> const& arr ) {
return reverse_impl( arr, typename make_index_tuple<N>::type() );
}
◆IndexTuple イディオム
• IndexTupe イディオムで reverse の実装
template<class T, size_t N, ptrdiff_t... Indexes>
constexpr array<T, N>
reverse_impl( array<T, N> const& arr, index_tuple<Indexes...> ) {
return array<T, N>{{ arr[N-1-Indexes]... }};
}
template<class T, size_t N>
constexpr array<T, N> reverse( array<T, N> const& arr ) {
return reverse_impl( arr, typename make_index_tuple<N>::type() );
}
インデックス列の
パラメータパック
パック展開式
◆IndexTuple イディオム
• 原理
– 型レベルで [0..N) のインデックス列を⽤意(テンプレートパラ
メータパック)
– 要素列 a[0], a[1], ..., a[N-1] に展開(パック展開式)
• 数学的意味
– 簡単に⾔えば自然数列 {0, 1, ..., N-1} から S への写像
a:{0, 1, ..., N-1} → S を定義すること
– reverse の場合は、入⼒を A とすると一般項 a[n] = A[N-1-n]
になる
◆IndexTuple イディオム
• 適⽤できる条件
– 入⼒が RandomAccessTraversal である(添字アクセス可能)
– 一般項の計算量がほぼ定数時間である
– 漸化式(例えばフィボナッチ数)などは一般に項 a[n] の計算量が
Ο(n) になるので適⽤できない
– n 番目の素数は一意に定まるが、n 番目の素数を定数時間で求
める方法はないので適⽤できない
• 計算量
– 再帰深度は定数オーダー Ο(1)
– ただし、テンプレートインスタンス化の再帰深度が高々対数
オーダー Ο(log n)
◆IndexTuple イディオム
• ライブラリ
– C++14 の integer_sequence
• N3658 の提案では、Efficiency considerations として計算量
Ο(log2 n) の実装について考察
– Sprout の index_tuple
• integer_sequence とほぼ互換のインタフェース
• 様々なユーティリティ
• 計算量 log2(n) の保証
◆倍分再帰
• 倍分再帰で distance の実装
template<typename InputIterator>
constexpr pair<InputIterator, typename iterator_traits<InputIterator>::difference_type>
distance_impl_1(
pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> const& current,
InputIterator last, typename iterator_traits<InputIterator>::difference_type n
)
{
typedef pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> type;
return current.first == last ? current
: n == 1 ? type(next(current.first), current.second + 1)
: distance_impl_1(
distance_impl_1(
current,
last, n / 2 /* 左側を検索 */
),
last, n - n / 2 /* 右側を検索 */
)
;
}
◆倍分再帰
• 倍分再帰で distance の実装
template<typename InputIterator>
constexpr pair<InputIterator, typename iterator_traits<InputIterator>::difference_type>
distance_impl(
pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> const& current,
InputIterator last, typename iterator_traits<InputIterator>::difference_type n
)
{
typedef pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> type;
return current.first == last ? current
: distance_impl(
distance_impl_1(
current,
last, n /* 検索範囲 n を検索 */
),
last, n * 2 /* 次の検索範囲 n * 2 を検索 */
)
;
}
◆倍分再帰
• 倍分再帰で distance の実装
template<typename InputIterator>
constexpr typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last) {
typedef pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> type;
return distance_impl(type(first, 0), last, 1).second;
}
◆倍分再帰
• 原理
– サイクル 1 :与えられた範囲を⼆分検索する
– サイクル 2 :試⾏範囲を倍々しながらサイクル 1 を呼ぶ
◆倍分再帰
• 基本的には⼆分検索(サイクル 1)
• ただし、範囲が未知なので試⾏範囲を指数的に増やして
いく(サイクル 2)
– 現在の範囲で終了しなかったら、右側に同じだけ拡張
• サイクル 1 は Ο(log n)、サイクル 2 も Ο(log n) なの
で、合わせた計算量も Ο(log n) になる
• 試⾏範囲を「⼆倍→⼆分」繰り返すので、「倍分再帰」
と名付けた(造語)
◆倍分再帰
• 適⽤できる条件
– 入⼒が SinglePassTraversal 以上である
• 計算量
– 再帰深度はほぼ対数オーダー Ο(log2 n)
– ただしイテレータ同士の⽐較回数が最大で Ο(2^ceil(log2 n))
• STL で同等のアルゴリズムだと Ο(n)
• n が 2 の冪乗の場合は同じ計算量だが、それ以外だと若⼲非効率
◆アルゴリズム
• 変更を伴わないアルゴリズム
• 変更を伴うアルゴリズム
• RangeAdaptor
◆変更を伴わないアルゴリズム
• <algorithm> や <numeric> の Non-modifying
sequence operations
– all_of, find など
• <cstring>
– strlen, strcmp など
• インタフェース
– 標準ライブラリとほぼ同じにできる
◆変更を伴わないアルゴリズム
• 処理をディスパッチ
– 入⼒が RandomAccessTraversal である
• → 単純な⼆分検索
– それ以外
• → 倍分再帰
• 計算量
– 入⼒が一つの範囲の場合 → Ο(log2 n)
– 入⼒が⼆つの範囲の場合 → Ο(log2 n+m)
– 標準ライブラリとほぼ同じ効率で実装できる
◆変更を伴うアルゴリズム
• <algorithm> や <numeric> の Mutating sequence
operations
– reverse, transform, sort など
• インタフェース
– 出⼒をコンテナにコピーして返すような、副作⽤のない形にす
る必要がある
void sort( Iterator first, Iterator last );
// 副作用がある
constexpr Container sort( Container const& cont );
// 副作用がない
◆変更を伴うアルゴリズム
• 出⼒が添字に対して一意に定義できる場合
– reverse など
– 入⼒が RandomAccessTraversal である
• → IndexTuple イディオム
– それ以外
• → 愚直に線形再帰
• 出⼒が添字に対して一意に定義できない場合
– sort など
– 愚直に線形再帰
◆変更を伴うアルゴリズム
• 計算量
– IndexTuple イディオムが使える場合
• → Ο(n)
– それ以外
• → アルゴリズムによる
• 問題点
– クイックソートなど In-place アルゴリズムが使えない
– 出⼒を常にコピーするため、オブジェクトコピーの計算量が
Ο(n) 余計に必要
◆RangeAdaptor
• Boost.Range や Pstade.Oven の実装が典型的
• RangeAdaptor の適⽤自体は通常副作⽤を持たず、最終
的に評価される時点で実処理が⾏われる
◆RangeAdaptor
• C++ における Range は、しばしば [first, last) といっ
たイテレータを組にしたクラスとして表現される
– Range のクラスそのものは状態を変更しないことが多い
– D ⾔語の Range では、イテレータではなく Range そのものが
状態を持っている
struct MyRange {
constexpr iterator begin() const;
constexpr iterator end() const;
};
◆RangeAdaptor
• 要素コピー回数の⽐較
• 通常のアルゴリズムの場合
– コピー回数 = 10 × 3
• RangeAdaptor の場合
– コピー回数 = 10
constexpr auto a1 = iota<array<int, 10>>( 1 ); // 10回
constexpr auto a2 = reverse( a1 ); // 10回
constexpr auto a = range::transform(
a2, a2, bind2nd( multiplies<>(), 2 ) ); // 10回
constexpr array<int, 10> a =
counting( 1, 10 )
| reversed
| transformed( bind2nd( multiplies<>(), 2 ) )
| copied;
◆RangeAdaptor
• reversed アダプタの実装例
template<class BaseRange>
class reversed_range {
constexpr reverse_iterator begin() const;
constexpr reverse_iterator end() const;
};
class reversed_forwarder {};
static constexpr reversed_forwarder reversed = {};
template<class BaseRange>
constexpr reversed_range<BaseRange>
operator| ( BaseRange const& rng, reversed_forwarder ) {
return reversed_range<BaseRange>( rng );
}
◆RangeAdaptor
• reversed アダプタの実装例
template<class BaseRange>
class reversed_range {
constexpr reverse_iterator begin() const;
constexpr reverse_iterator end() const;
};
class reversed_forwarder {};
static constexpr reversed_forwarder reversed = {};
template<class BaseRange>
constexpr reversed_range<BaseRange>
operator| ( BaseRange const& rng, reversed_forwarder ) {
return reversed_range<BaseRange>( rng );
}
reversed 自体は機能を持たず、
operator|() を ADL で呼ぶ為
のタグとして使われる
reversed_range は、範囲
を逆順に辿るイテレータ
を保持する
operator|() によって、
reversed_range を生成
して返す
◆RangeAdaptor
• 目的の動作をするイテレータさえ実装できれば、
RangeAdaptor も簡単に実装できる
◆クラス設計
• イテレータインタフェース
• データメンバアクセス
• メソッドチェイン
◆イテレータインタフェース
• 通常副作⽤を持たない
– デリファレンス: *a
– 添字デリファレンス: a[n]
– 加算/減算: a + n, a - n, a - b
– 等価⽐較: a == b, a != b
– 不等価⽐較: a < b, a > b, a <= b, a >= b
• 通常副作⽤を持つ
– インクリメント/デクリメント: ++a, --a, a++, a--
– 加算代入/減算代入: a += n, a -= n
◆イテレータインタフェース
• 案 1 :++/-- の代わりにメンバ関数 next/prev を持た
せる
– 欠点:同じメンバ関数を持っていない、他の全てのイテレータ
を統一的に扱えない
struct MyIterator {
constexpr MyIterator next() const;
constexpr MyIterator prev() const;
};
◆イテレータインタフェース
• 案 2 :フリー関数 iterator_next/iterator_prev をADL
でルックアップ
– 利点:他のライブラリのイテレータでもアダプトすることがで
きる
– 欠点:邪悪な ADL に依存する
– 名前を next/prev にしない理由は、一般的すぎる名前だと ADL
で余計なものまで候補になるおそれがある為
• Sprout では案 2 を採⽤している
constexpr MyIterator iterator_next(MyIterator);
constexpr MyIterator iterator_prev(MyIterator);
◆イテレータインタフェース
• 挙動をフォールバックする汎⽤ next フリー関数
– ADL で iterator_next(a) が呼べるか
• → iterator_next(a)
– a が RandomAccessIterator かつリテラル型か
• → it + 1
– それ以外
• → std::next(a)
template<class ForwardIterator>
constexpr ForwardIterator next(ForwardIterator it);
◆データメンバアクセス
• 通常のメンバ関数では、データメンバアクセスを完全に
代替することは出来ない
◆データメンバアクセス
• 公開されたデータメンバの場合
template<class T>
struct Holder {
T value;
};
constexpr int i = Holder<int>{ 100 }.value; // データメンバを参照
◆データメンバアクセス
• メンバ関数でアクセスする場合
– ill-formed !!!
template<class T>
struct Holder2 {
T v_;
constexpr T const& value() const { return v_; } /* const版 */
constexpr T& value() { return v_; } /* 非const版 */
};
◆データメンバアクセス
• メンバ関数でアクセスする場合
– ill-formed !!!
• 返値型のみが異なるメンバ関数の定義と⾒做され、コン
パイルエラーになる
template<class T>
struct Holder2 {
T v_;
constexpr T const& value() const { return v_; } /* const版 */
constexpr T& value() const { return v_; } /* 非const版 */
};
constexpr メンバ関数が
暗黙で const 修飾される
◆データメンバアクセス
• 非 const 版を constexpr 指定しない方法
template<class T>
struct Holder2 {
T v_;
constexpr T const& value() const { return v_; } /* const 版 */
T& value() { return v_; } /* 非 const 版 */
};
◆データメンバアクセス
• 非 const 版を constexpr 指定しない方法
• 非 const な prvalue や rvalue 参照からのメンバ関数呼
び出しは、非 const 版が優先される
template<class T>
struct Holder2 {
T v_;
constexpr T const& value() const { return v_; } /* const 版 */
T& value() { return v_; } /* 非 const 版 */
};
constexpr int i = Holder2<int>{ 100 }.value(); // Oops! 非 const 版が呼ばれる
◆データメンバアクセス
• static メンバ関数を使う方法
template<class T>
struct Holder3 {
T v_;
static constexpr T const& value(Holder3 const& t) { return t.v_; }
/* const 版 */
static constexpr T& value(Holder3& t) { return t.v_; }
/* 非 const 版 */
};
◆データメンバアクセス
• static メンバ関数を使う方法
template<class T>
struct Holder3 {
T v_;
static constexpr T const& value(Holder3 const& t) { return t.v_; }
/* const 版 */
static constexpr T& value(Holder3& t) { return t.v_; }
/* 非 const 版 */
};
constexpr int i = Holder3<int>::value( Holder3<int>{ 100 } ); // OK!
かなり書きづらい
template<class T>
constexpr T const& value(Holder3<T> const& t) {
return Holder3<T>::value(t); /* const版 */
}
template<class T>
constexpr T& value(Holder3<T>& t) {
return Holder3<T>::value(t); /* 非 const 版 */
}
◆データメンバアクセス
• フリー関数を使う方法
template<class T>
constexpr T const& value(Holder3<T> const& t) {
return Holder3<T>::value(t); /* const版 */
}
template<class T>
constexpr T& value(Holder3<T>& t) {
return Holder3<T>::value(t); /* 非 const 版 */
}
◆データメンバアクセス
• フリー関数を使う方法
constexpr int i = value( Holder3<int>{ 100 } ); // スッキリ!
◆データメンバアクセス
• C++14 では、constexpr メンバ関数が暗黙 const 修飾
されなくなる!
constexpr int i = Holder3<int>{ 100 }.value();
template<class T>
struct Holder4 {
T v_;
constexpr T const& value() const { return v_; } /* const 版 */
constexpr T& value() { return v_; } /* 非 const 版 */
};
こうあって然るべき
◆メソッドチェイン
• メソッドチェインでデータを処理するクラスの例
– const 版は次の状態のオブジェクトを返す
– 非 const 版は内部状態を変更する
struct Hasher {
constexpr Hasher process( Data const& src ) const; /* const 版 */
constexpr void process( Data const& src ); /* 非 const 版 */
constexpr Hashed finish() const; /* 結果を返す */
};
◆メソッドチェイン
• メソッドチェインでデータを処理するクラスの例
constexpr auto hashed =
Hasher()
.process(src1)
.process(src2)
.finish();
◆メソッドチェイン
• メソッドチェインでデータを処理するクラスの例
– ill-formed !!!
– constexpr の有無では解決できない
constexpr auto hashed =
Hasher()
.process(src1)
.process(src2)
.finish(); 非 const な prvalue や rvalue 参照から
のメンバ関数呼び出しは、非 const 版
が優先される
◆メソッドチェイン
• const 修飾された prvalue を返す方法
struct Hasher {
constexpr Hasher const process( Data const& src ) const; /* const 版 */
constexpr void process( Data const& src ); /* 非 const 版 */
constexpr Hashed finish() const; /* 結果を返す */
};
const 修飾
◆メソッドチェイン
• const 修飾された prvalue を返す方法
– constexpr メンバ関数でメソッドチェインを⾏う場合、返値は
const 修飾されたオブジェクトにすべき
constexpr auto hashed =
as_const(Hasher())
.process(src1)
.process(src2)
.finish(); OK!
◆その他
• ポインタの扱い
• 浮動小数点数の扱い
◆ポインタの扱い
• 定数式評価においては、ポインタ型は
RandomAccessIterator の仕様を満たさない
p1 < p2 // compile error!
p1 - p2 // compile error!
順序⽐較が出来ない
ポインタ同士の減算が
出来ない
◆ポインタの扱い
• 案 1 : distance(p1, p2) で距離計算する
– 毎回線形オーダー Ο(n) の計算を要する
– p1 <= p2 であることを保証しなければならない
◆ポインタの扱い
• 案 2 : 基準アドレスと基準からの距離を持ったイテ
レータでラップする
– ⽐較や減算は、予め計算された距離をもとに算出する
◆ポインタの扱い
• 案 2 : 基準アドレスと基準からの距離を持ったイテ
レータでラップする
– ⽐較や減算は、予め計算された距離をもとに算出する
– それ Sprout でできるよ!
#include <sprout/range.hpp>
constexpr int a[2] = { 1, 2 };
constexpr auto rng = range::make_ptr_range( a ); // RandomAccess
◆浮動小数点数の扱い
• 定数式評価で浮動小数点例外を発生する処理はコンパイ
ルエラーになる
constexpr auto NaN = numeric_limits<double>::quiet_NaN();
NaN == NaN; // OK. (non-signaling)
NaN < NaN; // compile error! (signaling)
NaN の大小⽐較など
◆浮動小数点数の扱い
• 浮動小数点数を扱う定数式で NaN に対応させたかった
らまず最初に NaN チェック
• GCC のビルトイン数学関数は定数式として使えるが、
規格上浮動小数点例外が発生する呼出はコンパイルエ
ラーになる
– 例えば NaN や ±∞ などの極値を返すような呼出
– Sprout.Math の数学関数は常に non-signaling な実装なので、
常に定数式で使える
◆これからの constexpr の話
• C++14 で変わる constexpr
• C++14 constexpr の使⽤例
• 更にこれからの constexpr
◆C++14 で変わる constexpr
• ローカル変数の使⽤
• 副作⽤の許可
• 制御構文の使⽤
• void がリテラル型に
• メンバ関数の暗黙の const の撤廃
• 抽象マシンモデルの採⽤
• 標準ライブラリ
◆ローカル変数の使⽤
• ローカル変数の使⽤
constexpr double heron(double a, double b, double c) {
double s = (a+b+c)/2;
return sqrt(s*(s-a)*(s-b)*(s-c) );
}
◆副作⽤の許可
• 副作⽤の許可
• ただし、オブジェクトの寿命が定数式評価中に始まるも
のでなければならない
– 定数式評価の外に副作⽤を及ぼすことは出来ない
– 定数式評価全体で副作⽤がないことが保証される
template<class Iterator, class Distance>
constexpr void advance( Iterator& it, Distance n ) {
it += n;
}
◆制御構文の使⽤
• 制御構文の使⽤
• if, switch, while, do-while, for, range-based for
template<class Range, class T>
constexpr T accumulate( Range const& rng, T init ) {
for ( auto const& e : rng ) {
init += e;
}
return init;
}
◆void がリテラル型に
• void がリテラル型に
• 返値が void の関数も constexpr 指定できる
template<class Iterator>
constexpr void sort( Iterator first, Iterator last );
◆メンバ関数の暗黙の const の撤廃
• メンバ関数の暗黙の const の撤廃
• ただし、現時点のドラフトでは標準ライブラリのインタ
フェースまで更新されていない
template<class T, size_t N>
struct array {
constexpr T const& front() const;
constexpr T& front();
};
◆抽象マシンモデルの採⽤
• C++11では、constexpr 関数の評価は、関数呼び出し
の置換(function invocation substitution)という規則で
定義されていた
– 関数の評価を、呼び出し先の関数の式の評価と置換することで、
関数呼び出しをエミュレートする
– 式変形の最適化に近い
• C++14では、定数式の評価はC++抽象マシンのサブ
セットとして再定義される
◆標準ライブラリ
• 新たに constexpr 指定されるもの
– forward, move, move_if_noexcept
– tuple, pair のほとんどの機能
– initializer_list
◆C++14 constexpr の使⽤例
• 可変⻑前方向リスト
#include <sprout/forward_clist.hpp>
constexpr int f() {
using namespace sprout;
auto li = forward_clist<int>();
{
decltype(li)::item it{ 10 };
li.push_front(it);
// { 10 }
{
array<decltype(li)::item, 5> its{{ 100, 200, 300, 400, 500 }};
li.insert_after(li.begin(), its.begin(), its.end());
// { 10, 100, 200, 300, 400, 500 }
/* ... */
li.unlink(its.begin(), its.end());
}
li.unlink(it);
}
}
◆C++14 constexpr の使⽤例
• 可変⻑前方向リスト
#include <sprout/forward_clist.hpp>
constexpr int f() {
using namespace sprout;
auto li = forward_clist<int>();
{
decltype(li)::item it{ 10 };
li.push_front(it);
// { 10 }
{
array<decltype(li)::item, 5> its{{ 100, 200, 300, 400, 500 }};
li.insert_after(li.begin(), its.begin(), its.end());
// { 10, 100, 200, 300, 400, 500 }
/* ... */
li.unlink(its.begin(), its.end());
}
li.unlink(it);
}
}
ノードはスタック上に無ければならな
い(動的オブジェクトが使えない)ので、
ローカル変数として宣⾔
RAII が使えないので手動でリンク解除
が必要
◆更にこれからの constexpr
• C++1y で応⽤される constexpr
• C++14 constexpr の問題点
• 標準ライブラリの更なる constexpr 化
• 期待しうる constexpr の進化
◆C++1y で応⽤される constexpr
• N3627 : switch 文での利⽤
– constexpr operator==() で⽐較できるリテラル型を switch
文で使えるようにする提案
• N3580 : Concept Lite
– 型の制約を constexpr 関数で表現する軽量コンセプトの提案
◆C++14 constexpr の問題点
• 例外安全性の問題
– ローカル変数と副作⽤が使えるようになったが、
ユーザ定義デストラクタが使えない
• つまり RAII が使えない
– try-catch ブロックが書けない
• つまり例外をハンドリングできない
– 例外安全でない constexpr 関数を簡単に書くことが
出来る
– そのような関数を、インタフェースを変えずに例外
安全に書き直すのも困難
◆標準ライブラリの更なる constexpr 化
• メンバ関数の暗黙の const 修飾が撤廃されたた
め、constexpr メンバ関数のオーバーロードは、
ほぼ無条件で constexpr 指定を付け加えられる
と考えられる
• イテレータ関連の機能
◆期待しうる constexpr の進化
• ラムダ式
– 相変わらずマングルの問題で⾒送られているが、議
論は続けられている
• new/delete
– N3664(new のメモリ確保を実装が省略可能にする)
が採択されたので、コンパイル時に処理系が
Dynamic storage duration 以外の記憶期間を使え
るよう変更されれば、コンパイル時 new も許可され
るかもしれない
◆期待しうる constexpr の進化
• ユーザ定義リテラル
– テンプレートパラメータパックを受け取るユーザ定
義文字列リテラル
• コンパイル時 IO
– 欲しい
◆まとめ
• C++11 のもっとも重要な新機能の一つである
constexpr は、様々なテクニックを駆使して何
でも出来ることが示された
• C++14 では、constexpr が書きやすくなり、
モデルも刷新され、よりユーザフレンドリに
• C++1y 以降では、constexpr の更なる進化が
望める
• constexpr の今後に大いに期待しながらエン
ジョイしよう!!
ご清聴ありがとう
ございました

Weitere ähnliche Inhalte

Was ist angesagt?

組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
kikairoya
 
20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン
yohhoy
 
組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門
Norishige Fukushima
 
オブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメ
Yoji Kanno
 

Was ist angesagt? (20)

ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
 
Glibc malloc internal
Glibc malloc internalGlibc malloc internal
Glibc malloc internal
 
プログラムを高速化する話
プログラムを高速化する話プログラムを高速化する話
プログラムを高速化する話
 
不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
 
20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン
 
組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門
 
高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装
 
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
 
明日使えないすごいビット演算
明日使えないすごいビット演算明日使えないすごいビット演算
明日使えないすごいビット演算
 
RSA暗号運用でやってはいけない n のこと #ssmjp
RSA暗号運用でやってはいけない n のこと #ssmjpRSA暗号運用でやってはいけない n のこと #ssmjp
RSA暗号運用でやってはいけない n のこと #ssmjp
 
C++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミング
 
何となく勉強した気分になれるパーサ入門
何となく勉強した気分になれるパーサ入門何となく勉強した気分になれるパーサ入門
何となく勉強した気分になれるパーサ入門
 
BoostAsioで可読性を求めるのは間違っているだろうか
BoostAsioで可読性を求めるのは間違っているだろうかBoostAsioで可読性を求めるのは間違っているだろうか
BoostAsioで可読性を求めるのは間違っているだろうか
 
Effective Modern C++ 勉強会 Item 22
Effective Modern C++ 勉強会 Item 22Effective Modern C++ 勉強会 Item 22
Effective Modern C++ 勉強会 Item 22
 
async/await のしくみ
async/await のしくみasync/await のしくみ
async/await のしくみ
 
オブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメ
 
C# 8.0 非同期ストリーム
C# 8.0 非同期ストリームC# 8.0 非同期ストリーム
C# 8.0 非同期ストリーム
 
C++の話(本当にあった怖い話)
C++の話(本当にあった怖い話)C++の話(本当にあった怖い話)
C++の話(本当にあった怖い話)
 

Ähnlich wie Constexpr 中3女子テクニック

リテラル文字列型までの道
リテラル文字列型までの道リテラル文字列型までの道
リテラル文字列型までの道
Satoshi Sato
 
197x 20090704 Scalaで並行プログラミング
197x 20090704 Scalaで並行プログラミング197x 20090704 Scalaで並行プログラミング
197x 20090704 Scalaで並行プログラミング
Net Penguin
 
Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~
CHY72
 
シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)
icchy
 
Lisp tutorial for Pythonista : Day 1
Lisp tutorial for Pythonista : Day 1Lisp tutorial for Pythonista : Day 1
Lisp tutorial for Pythonista : Day 1
Ransui Iso
 
Lisp tutorial for Pythonista : Day 2
Lisp tutorial for Pythonista : Day 2Lisp tutorial for Pythonista : Day 2
Lisp tutorial for Pythonista : Day 2
Ransui Iso
 
Real World OCamlを読んでLispと協調してみた
Real World OCamlを読んでLispと協調してみたReal World OCamlを読んでLispと協調してみた
Real World OCamlを読んでLispと協調してみた
blackenedgold
 
Scalaで萌える関数型プログラミング[1.1.RC1]
Scalaで萌える関数型プログラミング[1.1.RC1]Scalaで萌える関数型プログラミング[1.1.RC1]
Scalaで萌える関数型プログラミング[1.1.RC1]
Ra Zon
 

Ähnlich wie Constexpr 中3女子テクニック (20)

リテラル文字列型までの道
リテラル文字列型までの道リテラル文字列型までの道
リテラル文字列型までの道
 
Start!! Ruby
Start!! RubyStart!! Ruby
Start!! Ruby
 
197x 20090704 Scalaで並行プログラミング
197x 20090704 Scalaで並行プログラミング197x 20090704 Scalaで並行プログラミング
197x 20090704 Scalaで並行プログラミング
 
Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~
 
最近の単体テスト
最近の単体テスト最近の単体テスト
最近の単体テスト
 
シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)
 
Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3
 
How wonderful to be (statically) typed 〜型が付くってスバラシイ〜
How wonderful to be (statically) typed 〜型が付くってスバラシイ〜How wonderful to be (statically) typed 〜型が付くってスバラシイ〜
How wonderful to be (statically) typed 〜型が付くってスバラシイ〜
 
C++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるC++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISる
 
JavaScriptクイックスタート
JavaScriptクイックスタートJavaScriptクイックスタート
JavaScriptクイックスタート
 
Lisp tutorial for Pythonista : Day 1
Lisp tutorial for Pythonista : Day 1Lisp tutorial for Pythonista : Day 1
Lisp tutorial for Pythonista : Day 1
 
タダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitタダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnit
 
Phantom Type in Scala
Phantom Type in ScalaPhantom Type in Scala
Phantom Type in Scala
 
Lisp tutorial for Pythonista : Day 2
Lisp tutorial for Pythonista : Day 2Lisp tutorial for Pythonista : Day 2
Lisp tutorial for Pythonista : Day 2
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説
 
Real World OCamlを読んでLispと協調してみた
Real World OCamlを読んでLispと協調してみたReal World OCamlを読んでLispと協調してみた
Real World OCamlを読んでLispと協調してみた
 
わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61
 
Scalaで萌える関数型プログラミング[1.1.RC1]
Scalaで萌える関数型プログラミング[1.1.RC1]Scalaで萌える関数型プログラミング[1.1.RC1]
Scalaで萌える関数型プログラミング[1.1.RC1]
 
Effective Modern C++ 勉強会#3 Item 15
Effective Modern C++ 勉強会#3 Item 15Effective Modern C++ 勉強会#3 Item 15
Effective Modern C++ 勉強会#3 Item 15
 
What is Metasepi?
What is Metasepi?What is Metasepi?
What is Metasepi?
 

Constexpr 中3女子テクニック