ドワンゴ社員
情報規格調査会 CS22/WG21 C++小委員会
エキスパートメンバー(2015年3月末まで)
C++11に入る予定だった機能
コンセプト、コンセプトマップ、axiom
テンプレート仮引数に意味を与える
concept Cat<T> { } ;
class Mike { } ; // 猫
class Linda_pp { } ; // 犬
// ミケは猫
concept_map Cat<Mike> { } ;
// エサを与える
template < Cat cat > void feed_cat( cat c ) ;
feed_cat( Mike{ } ) ; // OK、Mikeは猫なのでエサをあげる
feed_cat( Linda_pp{ } ) ; // エラー、Linda_ppは猫ではない
型に制約を与える
auto concept pointer_like<T>
{
typename value_type ;
value_type & operator *() ;
}
// ネストされた型名value_type
// value_type &を返すoperator *()
// を持つ型に対してのみ有効
template < pointer_like T >
struct X { } ;
型を制約にマッピングする
// 生ポインターにはネストされた型名がないのでマッピング
template < typename T >
concept_map pointer_like<T *>
{
typedef T value_type ;
}
型を制約にマッピングする
// 既存の別のコーディングスタイルの型をマッピング
struct Hoge_ptr
{
typedef int type ; // value_typeではない
type & get() ; // operator *ではない
} ;
// マッピング
concept_map pointer_like< Hoge_ptr >
{
typedef Hoge_ptr::type value_type ;
value_type & operator *( Hoge_ptr & hoge )
{
return hoge.get() ;
}
}
2009年7月13-18日のフランクフルト会議で、否決
理由
コンセプトマップは暗黙的に生成されるべきかどうか
コンセプトは、たまたま制約が一致するだけでは使えない。
concept has_value_type<T>
{
typename value_type ;
}
struct X { using value_type = int ; } ;
template < has_value_type T > struct Y { } ;
Y<X> obj ; // エラー
型をコンセプトに対応させるには
明示的にコンセプトマップを書かなければならない
たとえ、何もマッピングする必要がなかったとしても
// コンセプトマップの生成
concept_map has_value_type<X>
{ /* 別に中身は必要ない */ } ;
空っぽのコンセプトマップを
何故わざわざ手書きしなければならないのだ
コンセプトマップは常に暗黙に生成されるべきだ
単なるシグネチャの一致とコンセプトの合致するかどうかは別物だ
コンセプトマップは手で書くべきだ
たとえ中身が空っぽだったとしてもだ
暗黙に生成させる文法を用意すればええんちゃう?
// 明示的なコンセプトマップが必要
concept X<T> { }
X<int> x ; // エラー
// 暗黙的にコンセプトマップを生成
auto concept Y<T> { }
Y<int> y ; // OK
原理主義者「絶対に手で書け。暗黙は認めん」
暗黙派「空っぽの冗長な記述など文法上のノイズだ」
中道派「どっちかデフォルトにしとけばええんちゃう?」
コンセプト否決
ドラフトから除去
range-based forが割りを食う
C++11規格制定が一年半も延期
ちょっと風呂敷広げすぎたから
今度は身の丈に合わせて設計するわ
Concept TSドラフト
11月に書き直しになるので注意
型の制約チェックに特化
既存のメタプログラミング技法の延長で設計
テンプレート実引数が、期待する制約を満たさない場合
極めて読みにくいエラーメッセージが出力される
テンプレートコードの奥深くでエラーになるので読みづらい
制約を記述して、制約を満たすかどうかをチェックできるようにする
テンプレートが想定する型制約を呼び出し側が満たせない場合
わかりにくいエラーメッセージとなる。
template < typename T >
void f( )
{
T t1 ; // Tにはデフォルトコンストラクターが必要
T t2 = t1 ; // Tにはコピーコンストラクターが必要
// Tにはデストラクターが必要
}
現実のコードでは
テンプレートfはユーザーが直接呼び出すものではなく
コードの奥底深くに潜んでいる。
コンパイラー
「foo::bar::detail::f_impl<T, U>でT=hoge, U=some_poilcy<T>のときなんたらかんたら」
ろくろを回さなくてええねんで
template < typename T >
concept bool JustWorkConcept =
std::is_default_constructible_v<T> &&
std::is_copy_constructible_v<T> &&
std::is_destructible_v<T> ;
template < JustWorkConcept T >
void f()
{
T t1 ;
T t2 = t1 ;
}
コンパイラー
「JustWorkConceptを満たしてへんで」
符号の有無で処理を変えたい
従来のSFINAEを使うやり方
template < typename T >
std::enable_if_t< std::is_signed_v<T>, T >
f( T a, T b ) ;
template < typename T >
std::enable_if_t< std::is_unsigned_v<T>, T >
f( T a, T b ) ;
釉薬は必要ないんやで
template < typename T >
concept bool Signed = std::is_signed_v<T> ;
template < typename T >
concept bool Unsigned = std::is_unsigned_v<T> ;
// オーバーロード解決で考慮される
f( Signed a, Signed b ) ;
f( Unsigned a, Unsigned b ) ;
この資料はドワンゴ勤務中に作成した
この勉強会には会社の金で来た
concept指定子を使って定義
制約を記述する
既存のテンプレートの一部をコンセプト化
変数コンセプトと関数コンセプトがある
C++14の変数テンプレートをコンセプト化
concept指定子つきの変数テンプレート
template < typename T >
concept bool IntegerConcept = std::is_integer_v<T> ;
関数テンプレートをコンセプト化
concept指定子つきで関数テンプレート
template < typename T >
concept bool IntegerConcept( )
{
return std::is_integer_v<T> ;
}
テンプレート仮引数の違う関数コンセプトは複数書ける
template < typename T >
concept bool f() { ... }
template < typename T, typename U >
concept bool f() { ... }
template < typename ... Types >
concept bool f() { ... }
コンセプトを使うテンプレート仮引数
template < C c > struct X { } ;
&&か||が基本
定数式
型はbool
論理演算子の左右オペランドの式の型もbool
1 + 1 ; // bool型ではないので制約式ではない
true || 1 ; // 論理和のオペランドがbool型ではない
コンセプト定義の制約式の中だけで使える
SFINAEのシンタックスシュガー
様々な条件でsubstitutionを行える
すべての要求を満たせばtrue
満たさないならばfalse
template < typename T >
concept bool has_func()
{
return requires( T t )
{
t.func() ; // substituion対象
} ;
}
ローカル実引数を導入できる。
substitutionに使う便利な名前以外の意味はない。
template < typename T >
concept bool has_func()
{
return requires( T a, T b )
{
a + b ; // substitution対象
} ;
}
式が妥当であることを要求
妥当であればtrueを、そうでなけばfalseを返す
template < typename T >
concept bool C()
{
return requires( T t )
{
t + t ;
*t ;
t.func() ;
func(t) ;
t()()()()() ;
} ;
}
関連型が妥当であることを要求
要求できる関連型
ネストされた型名が妥当であることを要求
template < typename T >
concept bool C()
{
return requires()
{
typename T::type ;
typename T::value_type ;
typename T::result_type ;
} ;
}
妥当なクラステンプレート特殊化かエイリアステンプレートが存在するかどうか
template < typename T >
concept bool C = requires ( )
{
Temp<T> ;
} ;
こういうものが存在すべき
// Temp<T>のTはSomeConcept<T>を満たす
template < SomeConcept T >
struct Temp { } ;
こういうものが存在すべき
substitutionの結果、妥当な型であること
template < typename T >
using Temp = T ;
様々な要求
式が妥当であるかどうか
単純要求と同じ
requires( T t )
{
{ t + t } ;
}
要求した型になるかどうか
requires( T t )
{
{ t.get_number() } -> int ;
}
結果の型として
型要求と同じ
requires( T t )
{
{ *t } -> T::value_type & ;
}
式が無例外かどうか
requires ( T x, T y )
{
{ std::swap(x, y) } noexcept ;
}
式が定数式かどうか
requires ( T t )
{
constexpr { func(t) } ;
}
requires clauseを書ける
別のコンセプトを要求
template < typename T >
concept bool C = true ;
template < typename T >
concept bool D()
{
return requires ()
{
requires C<T> ;
} ;
}
テンプレート仮引数として
template < C T >
void f( T x ) ;
template < C T >
struct S ;
テンプレート宣言が必要ない
template < typename T > concept bool C = true ;
void f( C x ) ;
// template < C _C >
void f( _C x ) ;
別の型を取りたい場合には使えない
void f( C a, C b ) ;
// template < C _C >
// void f( _C a, _C b ) ;
関数テンプレートにのみ指定可能
template < typename T >
concept bool C = true ;
template < typename T >
void f()
requires C<T>
{ }
テンプレートの簡易的な記述
template < typename T >
concept bool C = true ;
C{T} void f() ;
// template < typename T >
// void f()
// requires C<T> ;
複数のテンプレート仮引数
template < typename A, typename B, typename ... C >
concept bool C = true ;
C{ A, B, C }
void f() ;
// tempalte < typename A, typename B, typename ... C >
// void f()
// requires C<A, B, C...> ;
atomic constraint単位の包括関係
template < typename T > concept bool A = std::is_fundamental_v<T> ;
template < typename T > concept bool B = A<T> && std::is_integer_v<T> ;
template < typename T > concept bool C = B<T> && std::is_same_v<T, int> ;
void f( A ) ;
void f( B ) ;
void f( C ) ;
f( 0.0 ) ; // A, double
f( 0L ) ; // B, long
f( 0 ) ; // C, int
C++11の機能
typedef名を宣言する新しい文法
// typedef int type ;
using type = int ;
エイリアス宣言のテンプレート化
template < typename T >
using type = T ;
type<int> t1 ; // int
type<double> t2 ; // double
型を返すtraitsのラッパーライブラリ
::typeがいらなくなる
template < typename T >
using add_pointer_t = typename std::add_pointer<T>::type ;
// add_pointer<int>::type
add_pointer_t<int> p ; // int *
C++98の機能
変数を宣言する新しい文法
int pi_i = 3 ;
double pi_d = 3.1415926535 ;
pi_i ;
pi_d ;
C++14の機能
変数宣言をテンプレート化する文法
template < typename T >
T pi = static_cast<T>( 3.1415926535 ) ;
pi<int> ;
pi<double> ;
値を返すtraitsを変数テンプレートでラップしたライブラリ
::valueがいらなくなる
template < typename T >
constexpr bool is_pointer_v = std::is_pointer<T>::value ;
template < typename T >
void f()
{
// is_pointer<T>::value
constexpr bool b = is_pointer_v<T> ;
}