Contenu connexe Similaire à Constexpr 中3女子テクニック Similaire à Constexpr 中3女子テクニック (20) Constexpr 中3女子テクニック2. ◆自己紹介
• 名前 : 村上 原野 (むらかみ げんや)
@bolero_MURAKAMI, id:boleros
• 棲息地: 大都会岡山
• 仕事 : 猪風来美術館陶芸指導員
・普段はろくろをまわしたり、
縄文土器をつくったりしています
・趣味は constexpr です
3. ◆自己紹介
• 公開しているライブラリ:
Sprout C++ Library (constexpr ライブラリ)
github.com/bolero-MURAKAMI/Sprout
• 前回発表資料:
Boost.勉強会 #7
【中3⼥⼦でもわかる constexpr】
Boost.勉強会 #8
【中3⼥⼦が狂える本当に気持ちのいい constexpr】
www.slideshare.net/GenyaMurakami
8. ◆constexpr で扱えるデータ
[リテラル型]
[スカラ型] [リテラル型の配列]
LiteralType [N]
[リテラル型への参照]
LiteralType const&
[算術型]
[整数型] int, unsigned int, char, ...
[浮動小数点型] float, double, ...
[ポインタ型]
[ポインタ] int const*, int (*)(void), ...
[メンバポインタ] int T::*, int (T::*)(void), ...
[列挙型] enum
特定の条件を満たす
ユーザ定義クラス
9. ◆constexpr を使うべき 5 の理由
• 明示的なコンパイル時定数の定義
• コンパイル時定数を返す関数
• 副作⽤がないことを保証する
• better TMP
• 初期化をあらかじめ⾏っておく
16. ◆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;
17. ◆better TMP
• ⾒慣れたプログラムと変わらない constexpr
constexpr auto src = make_array<int>( 1, 2, 3, 4, 5 );
constexpr auto accumulated =
range::accumulate(src, 0, plus<>() );
27. ◆実は副作⽤を禁止できない
• これもそのまま constexpr 関数に出来る
template<class T>
constexpr T& f( T&& t ) { return t = 42; }
constexpr int k = f(0);
もちろんコンパイル時に評価しよう
とするとエラーになるが……
int i = 0;
int j = f( i );
実⾏時評価だと何も問題なく
コンパイル・実⾏できる
29. ◆実は副作⽤を禁止できない
• constexpr 関数が副作⽤について保証するのは以下の場
合しかない
– コンパイル時に呼び出されたとき、副作⽤があればコンパイル
エラーになる
– コンパイル時に呼び出せる(副作⽤がない)とき、実⾏時にそれ
と等値な引数で呼び出しても、同じく副作⽤がない
• 実⾏時の constexpr 関数呼び出しで、副作⽤がないこ
とを証明するには以下の場合しかない
– コンパイル時に等値な引数で呼び出して、エラーにならないこ
とを確認する
– 実装を⾒て、全ての実⾏パスで副作⽤が起こりえないことを検
証する
31. ◆逆引き constexpr マニュアル
• constexpr で文字列を扱いたい
• constexpr で配列(コンテナ)を扱いたい
• constexpr でタプルを扱いたい
• constexpr でアルゴリズムを使いたい
• constexpr で RangeAdaptor を使いたい
• constexpr でアサーションを使いたい
• constexpr で数学関数を使いたい
• constexpr で乱数を使いたい
• constexpr でハッシュ関数を使いたい
• constexpr で UUID を使いたい
• constexpr で構文解析したい
• constexpr でレイトレーシングしたい
• constexpr で波形編集したい
32. ◆文字列を扱いたい
• それ 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"
33. ◆文字列を扱いたい
• 文字列→算術型の変換
• 算術型→文字列の変換
• その他、辞書順⽐較やストリーム入出⼒ 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
34. ◆文字列を扱いたい
• 所有権を持たない文字列クラス
– (C++1y の std::string_view 互換)
#include <sprout/utility/string_view.hpp>
constexpr auto s = string_view( "Hello world!" );
// -> string_view :型には要素数を含まない
35. ◆配列(コンテナ)を扱いたい
• それ 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();
36. ◆タプルを扱いたい
• それ 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
37. ◆アルゴリズムを使いたい
• それ 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 ) ),
"奇数であるか" );
38. ◆アルゴリズムを使いたい
• 変更を伴うアルゴリズム
• 変更を伴うアルゴリズムの 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 );
39. ◆アルゴリズムを使いたい
• 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 ) ),
"奇数であるか" );
40. ◆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..]
42. ◆アサーションを使いたい
• それ 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);
43. ◆アサーションを使いたい
• コンパイル時の場合→コンパイルエラー (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)
44. ◆数学関数を使いたい
• それ Sprout.Math で出来るよ!
• <cmath> の殆どの数学関数をサポート
• GCC のビルトイン関数が使える環境ならより高速に
#include <sprout/math.hpp>
constexpr auto v1 = cos(0.5); // 三角関数
constexpr auto v2 = tgamma(3.0); // ガンマ関数
45. ◆乱数を使いたい
• それ 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 ); // 正規分布
46. ◆乱数を使いたい
• 返値は { 生成値, 次の状態の生成器 } のペアになる
• 乱数列を 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;
48. ◆ハッシュ関数を使いたい
• 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" ) )();
49. ◆UUID を使いたい
• それ Sprout.Uuid で出来るよ!
• boost::uuids::uuid 互換のインタフェース
#include <sprout/uuid.hpp>
50. ◆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
51. ◆UUID を使いたい
• UUID ユーザ定義リテラル
constexpr auto id3 = "{550e8400-e29b-41d4-a716-446655440000}"_uuid;
constexpr auto id5 = "DNS"_uuid5( to_string( "boost.org" ) );
52. ◆構文解析したい
• そうだね、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_“ ) - "__“ )
// ゼロ個以上の英数字またはアンダースコア(アンダースコアは連続しない)
;
53. ◆構文解析したい
• パーザの適⽤
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(),
"マッチしない" );
58. ◆波形編集したい
• ナウなヤングにバカウケ Sprout.Compost
• 単純な正弦波の生成
#include <sprout/compost.hpp>
using namespace sprout::compost;
constexpr array<complex<double>, 256> src =
waves::sinusoidal( 10. / 256, 10000. ) | ranges::copied;
59. ◆波形編集したい
• DFT (離散フーリエ変換)
constexpr auto trans = src | analyses::dft | ranges::copied;
// 離散フーリエ変換
constexpr auto spec =
trans | analyses::amplitude_spectrum | ranges::copied;
// 振幅スペクトルに変換
60. ◆波形編集したい
• 単音の正弦波を生成
– 再生: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 // 任意のコンテナに変換可能にする
;
64. ◆波形編集したい
• 音声合成したい
– Rosenberg 波を音源波形とする(τ1 = 0.8, τ2 = 0.16)
– フォルマント(音声のスペクトルに固有のピーク)を⽤意する
– IIR フィルタ(無限インパルス応答において特定の周波数成分を
取り出す)でレゾナンスを掛ける
– 再生:vowel.wav
69. ◆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() );
}
70. ◆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() );
}
インデックス列の
パラメータパック
パック展開式
71. ◆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]
になる
72. ◆IndexTuple イディオム
• 適⽤できる条件
– 入⼒が RandomAccessTraversal である(添字アクセス可能)
– 一般項の計算量がほぼ定数時間である
– 漸化式(例えばフィボナッチ数)などは一般に項 a[n] の計算量が
Ο(n) になるので適⽤できない
– n 番目の素数は一意に定まるが、n 番目の素数を定数時間で求
める方法はないので適⽤できない
• 計算量
– 再帰深度は定数オーダー Ο(1)
– ただし、テンプレートインスタンス化の再帰深度が高々対数
オーダー Ο(log n)
73. ◆IndexTuple イディオム
• ライブラリ
– C++14 の integer_sequence
• N3658 の提案では、Efficiency considerations として計算量
Ο(log2 n) の実装について考察
– Sprout の index_tuple
• integer_sequence とほぼ互換のインタフェース
• 様々なユーティリティ
• 計算量 log2(n) の保証
74. ◆倍分再帰
• 倍分再帰で 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 /* 右側を検索 */
)
;
}
75. ◆倍分再帰
• 倍分再帰で 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 を検索 */
)
;
}
76. ◆倍分再帰
• 倍分再帰で 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;
}
78. ◆倍分再帰
• 基本的には⼆分検索(サイクル 1)
• ただし、範囲が未知なので試⾏範囲を指数的に増やして
いく(サイクル 2)
– 現在の範囲で終了しなかったら、右側に同じだけ拡張
• サイクル 1 は Ο(log n)、サイクル 2 も Ο(log n) なの
で、合わせた計算量も Ο(log n) になる
• 試⾏範囲を「⼆倍→⼆分」繰り返すので、「倍分再帰」
と名付けた(造語)
79. ◆倍分再帰
• 適⽤できる条件
– 入⼒が SinglePassTraversal 以上である
• 計算量
– 再帰深度はほぼ対数オーダー Ο(log2 n)
– ただしイテレータ同士の⽐較回数が最大で Ο(2^ceil(log2 n))
• STL で同等のアルゴリズムだと Ο(n)
• n が 2 の冪乗の場合は同じ計算量だが、それ以外だと若⼲非効率
81. ◆変更を伴わないアルゴリズム
• <algorithm> や <numeric> の Non-modifying
sequence operations
– all_of, find など
• <cstring>
– strlen, strcmp など
• インタフェース
– 標準ライブラリとほぼ同じにできる
82. ◆変更を伴わないアルゴリズム
• 処理をディスパッチ
– 入⼒が RandomAccessTraversal である
• → 単純な⼆分検索
– それ以外
• → 倍分再帰
• 計算量
– 入⼒が一つの範囲の場合 → Ο(log2 n)
– 入⼒が⼆つの範囲の場合 → Ο(log2 n+m)
– 標準ライブラリとほぼ同じ効率で実装できる
83. ◆変更を伴うアルゴリズム
• <algorithm> や <numeric> の Mutating sequence
operations
– reverse, transform, sort など
• インタフェース
– 出⼒をコンテナにコピーして返すような、副作⽤のない形にす
る必要がある
void sort( Iterator first, Iterator last );
// 副作用がある
constexpr Container sort( Container const& cont );
// 副作用がない
85. ◆変更を伴うアルゴリズム
• 計算量
– IndexTuple イディオムが使える場合
• → Ο(n)
– それ以外
• → アルゴリズムによる
• 問題点
– クイックソートなど In-place アルゴリズムが使えない
– 出⼒を常にコピーするため、オブジェクトコピーの計算量が
Ο(n) 余計に必要
87. ◆RangeAdaptor
• C++ における Range は、しばしば [first, last) といっ
たイテレータを組にしたクラスとして表現される
– Range のクラスそのものは状態を変更しないことが多い
– D ⾔語の Range では、イテレータではなく Range そのものが
状態を持っている
struct MyRange {
constexpr iterator begin() const;
constexpr iterator end() const;
};
88. ◆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;
89. ◆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 );
}
90. ◆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 を生成
して返す
93. ◆イテレータインタフェース
• 通常副作⽤を持たない
– デリファレンス: *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
94. ◆イテレータインタフェース
• 案 1 :++/-- の代わりにメンバ関数 next/prev を持た
せる
– 欠点:同じメンバ関数を持っていない、他の全てのイテレータ
を統一的に扱えない
struct MyIterator {
constexpr MyIterator next() const;
constexpr MyIterator prev() const;
};
95. ◆イテレータインタフェース
• 案 2 :フリー関数 iterator_next/iterator_prev をADL
でルックアップ
– 利点:他のライブラリのイテレータでもアダプトすることがで
きる
– 欠点:邪悪な ADL に依存する
– 名前を next/prev にしない理由は、一般的すぎる名前だと ADL
で余計なものまで候補になるおそれがある為
• Sprout では案 2 を採⽤している
constexpr MyIterator iterator_next(MyIterator);
constexpr MyIterator iterator_prev(MyIterator);
96. ◆イテレータインタフェース
• 挙動をフォールバックする汎⽤ next フリー関数
– ADL で iterator_next(a) が呼べるか
• → iterator_next(a)
– a が RandomAccessIterator かつリテラル型か
• → it + 1
– それ以外
• → std::next(a)
template<class ForwardIterator>
constexpr ForwardIterator next(ForwardIterator it);
100. ◆データメンバアクセス
• メンバ関数でアクセスする場合
– 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 修飾される
101. ◆データメンバアクセス
• 非 const 版を constexpr 指定しない方法
template<class T>
struct Holder2 {
T v_;
constexpr T const& value() const { return v_; } /* const 版 */
T& value() { return v_; } /* 非 const 版 */
};
102. ◆データメンバアクセス
• 非 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 版が呼ばれる
103. ◆データメンバアクセス
• 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 版 */
};
104. ◆データメンバアクセス
• 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!
かなり書きづらい
105. 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 版 */
}
◆データメンバアクセス
• フリー関数を使う方法
106. 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 } ); // スッキリ!
107. ◆データメンバアクセス
• 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 版 */
};
こうあって然るべき
108. ◆メソッドチェイン
• メソッドチェインでデータを処理するクラスの例
– const 版は次の状態のオブジェクトを返す
– 非 const 版は内部状態を変更する
struct Hasher {
constexpr Hasher process( Data const& src ) const; /* const 版 */
constexpr void process( Data const& src ); /* 非 const 版 */
constexpr Hashed finish() const; /* 結果を返す */
};
111. ◆メソッドチェイン
• 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 修飾
112. ◆メソッドチェイン
• const 修飾された prvalue を返す方法
– constexpr メンバ関数でメソッドチェインを⾏う場合、返値は
const 修飾されたオブジェクトにすべき
constexpr auto hashed =
as_const(Hasher())
.process(src1)
.process(src2)
.finish(); OK!
115. ◆ポインタの扱い
• 案 1 : distance(p1, p2) で距離計算する
– 毎回線形オーダー Ο(n) の計算を要する
– p1 <= p2 であることを保証しなければならない
116. ◆ポインタの扱い
• 案 2 : 基準アドレスと基準からの距離を持ったイテ
レータでラップする
– ⽐較や減算は、予め計算された距離をもとに算出する
117. ◆ポインタの扱い
• 案 2 : 基準アドレスと基準からの距離を持ったイテ
レータでラップする
– ⽐較や減算は、予め計算された距離をもとに算出する
– それ Sprout でできるよ!
#include <sprout/range.hpp>
constexpr int a[2] = { 1, 2 };
constexpr auto rng = range::make_ptr_range( a ); // RandomAccess
119. ◆浮動小数点数の扱い
• 浮動小数点数を扱う定数式で NaN に対応させたかった
らまず最初に NaN チェック
• GCC のビルトイン数学関数は定数式として使えるが、
規格上浮動小数点例外が発生する呼出はコンパイルエ
ラーになる
– 例えば NaN や ±∞ などの極値を返すような呼出
– Sprout.Math の数学関数は常に non-signaling な実装なので、
常に定数式で使える
121. ◆C++14 で変わる constexpr
• ローカル変数の使⽤
• 副作⽤の許可
• 制御構文の使⽤
• void がリテラル型に
• メンバ関数の暗黙の const の撤廃
• 抽象マシンモデルの採⽤
• 標準ライブラリ
124. ◆制御構文の使⽤
• 制御構文の使⽤
• 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;
}
125. ◆void がリテラル型に
• void がリテラル型に
• 返値が void の関数も constexpr 指定できる
template<class Iterator>
constexpr void sort( Iterator first, Iterator last );
126. ◆メンバ関数の暗黙の const の撤廃
• メンバ関数の暗黙の const の撤廃
• ただし、現時点のドラフトでは標準ライブラリのインタ
フェースまで更新されていない
template<class T, size_t N>
struct array {
constexpr T const& front() const;
constexpr T& front();
};
129. ◆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);
}
}
130. ◆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 が使えないので手動でリンク解除
が必要
132. ◆C++1y で応⽤される constexpr
• N3627 : switch 文での利⽤
– constexpr operator==() で⽐較できるリテラル型を switch
文で使えるようにする提案
• N3580 : Concept Lite
– 型の制約を constexpr 関数で表現する軽量コンセプトの提案
133. ◆C++14 constexpr の問題点
• 例外安全性の問題
– ローカル変数と副作⽤が使えるようになったが、
ユーザ定義デストラクタが使えない
• つまり RAII が使えない
– try-catch ブロックが書けない
• つまり例外をハンドリングできない
– 例外安全でない constexpr 関数を簡単に書くことが
出来る
– そのような関数を、インタフェースを変えずに例外
安全に書き直すのも困難
134. ◆標準ライブラリの更なる constexpr 化
• メンバ関数の暗黙の const 修飾が撤廃されたた
め、constexpr メンバ関数のオーバーロードは、
ほぼ無条件で constexpr 指定を付け加えられる
と考えられる
• イテレータ関連の機能
135. ◆期待しうる constexpr の進化
• ラムダ式
– 相変わらずマングルの問題で⾒送られているが、議
論は続けられている
• new/delete
– N3664(new のメモリ確保を実装が省略可能にする)
が採択されたので、コンパイル時に処理系が
Dynamic storage duration 以外の記憶期間を使え
るよう変更されれば、コンパイル時 new も許可され
るかもしれない