constexpr 中3女子テクニック
TRANSCRIPT
constexpr中3女子テクニック ―実践と濫⽤そしてC++14へ
Boost.勉強会
#12bolero_MURAKAMI2013/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" ); // 4constexpr 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" ) ); // 37564constexpr 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(); // 5constexpr auto i2 = a[2]; // 3constexpr 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; // 3constexpr 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 10constexpr 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_typedistance(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
の今後に大いに期待しながらエン ジョイしよう!!
ご清聴ありがとう ございました