帰ってきた

コンセプト

https://github.com/EzoeRyou/boost-benkyokai-oosaka-16

自己紹介

江添亮

ドワンゴ社員

情報規格調査会 CS22/WG21 C++小委員会
エキスパートメンバー(2015年3月末まで)

Conceptとは

岡山の陶芸家を用なしにする機能

Conceptとは

でちまるさんの兄にとどめを刺す機能

Conceptとは

C++11に入る予定だった機能

コンセプト、コンセプトマップ、axiom

C++0xコンセプト

テンプレート仮引数に意味を与える

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は猫ではない

C++0xコンセプト

型に制約を与える

auto concept pointer_like<T> 
{
    typename value_type ;
    value_type & operator *() ;
}

// ネストされた型名value_type
// value_type &を返すoperator *()
// を持つ型に対してのみ有効
template < pointer_like T >
struct X { } ;

C++0xコンセプトマップ

型を制約にマッピングする

// 生ポインターにはネストされた型名がないのでマッピング
template < typename T >
concept_map pointer_like<T *>
{
    typedef T value_type ;
}

C++0xコンセプトマップ

型を制約にマッピングする

// 既存の別のコーディングスタイルの型をマッピング
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() ;
    }
}

C++0xのAxiom

謎の機能

コンセプト否決

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月に書き直しになるので注意

https://github.com/cplusplus/concepts-ts

軽量コンセプト

型の制約チェックに特化

既存のメタプログラミング技法の延長で設計

呼び出し側の型制約チェック

テンプレート実引数が、期待する制約を満たさない場合

極めて読みにくいエラーメッセージが出力される

テンプレートコードの奥深くでエラーになるので読みづらい

制約を記述して、制約を満たすかどうかをチェックできるようにする

呼び出し型の型制約チェック

テンプレートが想定する型制約を呼び出し側が満たせない場合

わかりにくいエラーメッセージとなる。

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> ;

変数コンセプトの条件

  • 非制約テンプレート
  • 型はbool
  • 宣言には初期化子が必須
  • 初期化子は制約式

関数コンセプト

関数テンプレートをコンセプト化

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() { ... } 

関数コンセプトの条件

  • 非制約テンプレート
  • 戻り値の型はbool
  • 仮引数なし
  • 宣言は定義
  • 再帰不可
  • 制約式のreturn文ひとつのみ

制約テンプレート

コンセプトを使うテンプレート仮引数

template < C c > struct X { } ;

制約式

&&か||が基本

定数式

型はbool

論理演算子の左右オペランドの式の型もbool

1 + 1 ; // bool型ではないので制約式ではない
true || 1 ; // 論理和のオペランドがbool型ではない

requires式

コンセプト定義の制約式の中だけで使える

SFINAEのシンタックスシュガー

様々な条件でsubstitutionを行える

すべての要求を満たせばtrue

満たさないならばfalse

実例

template < typename T >
concept bool has_func()
{
    return requires( T t )
    {
        t.func() ; // substituion対象
    } ;
}

requires式

ローカル実引数を導入できる。

substitutionに使う便利な名前以外の意味はない。

template < typename T >
concept bool has_func()
{
    return requires( T a, T b )
    {
        a + b ; // substitution対象
    } ;
}

requires式

  • 単純要求
  • 型要求
  • 複合要求
  • ネスト要求

単純要求

式が妥当であることを要求

妥当であれば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 ) ;

requires clause

関数テンプレートにのみ指定可能

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...> ;

subsume

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

::typeのラッパー

型を返す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> ;

::valueのラッパー

値を返す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> ;
}