【C++ Advent Calendar 2016 13日目】C++ で楽しく Expression Templates しよう
C++ Advent Calendar 2016 13日目の記事です。
13日目が空いてるようなのでちょっと Expression Templates というマニアックなネタでも。
まあ C++ プロの方なら Expression Templates なんて余裕ですよね。
[注意]
本記事のコードは基本的には C++14 に準じているので、自環境で試したい場合は注意してください。
[Expression Templates とは]
Expression Templates とは wikibooks から引用すると
C/C++ において数学的な式を評価するありふれたやり方として、関数中に式をラップし、その関数ポインタを渡して、入力された数値群に対して適用するというものがある。この方法では、関数呼び出しと一時オブジェクト生成のオーバーヘッドが生じる。また、ソース中の式の位置が呼び出し元から非常に離れていることも頻繁であり、可読性と保守性に悪影響を与える。式テンプレート(Expression template)は、式をその場に展開することによって、関数ポインタを不要にし、式と呼び出し元をまとめることで問題を解決する。
式テンプレートには再帰的な型の合成(Recursive Type Composition)が用いられている。再帰的な型の合成では、少量のコードから複雑な型の階層を生じさせることができる。一方向の再帰的な型の合成では線形な型リスト(Type List)が生成される。二方向の再帰的な型の合成は二項式に対して有用であり、以下の例で用いている。メタプログラミングテクニックはデータのように型を合成することを可能にする。違いは、メタプログラムはコンパイル時に動作すること、またそれゆえデータではなく型に対してはたらくことだけである。
参照:More C++ Idioms/式テンプレート(Expression-template) - Wikibooks)
なるほどよくわからん。
と、言うことでこういう概念はすっ飛ばして、実際にコードを書いていってみます。
ちなみにこの記事は『Expression Templates とはなんぞやを解説する記事』ではなくて『C++14 でオレオレ Boost.Lambda を実装する』という記事になります。
[Boost.Lambda/Phoenix]
実際にコードを書いていく前に少し Boost.Lambda と Boost.Phoenix を解説します。
Boost.Lambda と Boost.Phoenix はまさに Expression Templates を利用して『ラムダ式(のようなもの)を定義するため』につくられたライブラリです。
ちなみに Boost.Lambda と Boost.Phoenix の2種類のライブラリが存在するがここでは Boost.Phoenix について説明します。
Boost.Phoenix は『プレースホルダ』と呼ばれるものを利用して式を定義していきます。
// この argN というのがプレースホルダになる // このプレースホルダはあとから評価した時の引数に着替えられる auto plus = arg1 + arg2 * arg3; // この時に arg1 は第一引数、arg2 は第二引数…という感じに置き換えられ // 最終的には 1 + 2 * 3 という式が評価される plus(1, 2, 3); // 7
このプレースホルダは何かというと単なる関数オブジェクトになり
arg1(1, 2, 3); // 1 arg2(1, 2, 3); // 2 arg3(1, 2, 3); // 3
という風に『第n番目の引数を返す』という挙動になります。
また、この他にも
// 値を返すだけの関数オブジェクトを返す auto _42 = val(42); // 引数の値を返すだけ _42(); // 42
という関数オブジェクトを返す val()
という関数も存在します。
この関数オブジェクトも先ほどのプレースホルダと組み合わせて使用できます。
// 関数オブジェクトから演算子を使って式を組み立てていく auto expr = arg1 + arg2 * val(42) expr(1, 2); // 85
こんな感じで Boost.Phoenix にはいろいろな関数オブジェクトが用意されており、それを組み合わせて式を定義していく感じになります。
ここで注意するのはすべて『関数オブジェクト』として扱うということです。
この点はかなり重要なので覚えておくとよいです。
それでは実際にこのような関数オブジェクトを実装してみます。
[val()
定義する]
まずは比較的な簡単な val()
の定義から。
まあこれは単に値を保持して返すだけのオブジェクトになります。
template<typename T> struct holder{ auto operator ()(...) const{ return value; } T value; }; template<typename T> holder<T> val(T t){ return {t}; } auto result = val(42)();
まあ値を保持する holder
クラスを用意して、それを生成するための val()
関数をヘルパ関数として定義してるだけですね。
この実装のキモとしては『実際の関数オブジェクトとして定義するクラス』と『その関数オブジェクトを返す関数』をわけて考えてることです。
これにより使用する側はクラステンプレートを意識する必要がありません。
あ、あと operator ()
が『可変長引数』を受け取っているのもポイントです。
これはあとから説明します。
ちなみに本筋とは関係ないところでコード量が増えてしまうので rvalue reference は省略して書いていきます。
まあ本物の C++ なら rvalue reference に対応するなど容易いはず
[プレースホルダオブジェクトを定義する]
次はプレースホルダです。
val()
と比較してちょっと複雑ですが、そこまで難しくはないです。
template<std::size_t Index> struct placeholder{ template<typename... Args> auto operator ()(Args... args) const{ // n番目の引数を受け取る実装に tuple を利用する return std::get<Index>(std::make_tuple(args...)); } }; const auto arg1 = placeholder<0>{}; const auto arg2 = placeholder<1>{}; const auto arg3 = placeholder<2>{}; arg1(1, 2, 3); // 1 arg2(1, 2, 3); // 2 arg3(1, 2, 3); // 3
こちらは operator ()
の引数を可変長テンプレート引数で受け取って std::tuple
と std::get
を利用して『任意の引数値を返す』という関数オブジェクトになっています。
また、これも『placeholder
クラス』と『arg1
, arg2
... オブジェクト』と切り分けて実装しています。
テンプレートを使用することでこういう『連番』のオブジェクトも比較的簡単に定義することが出来ますね。
[operator+
を定義]
さて、最後に関数オブジェクトを演算するための operator +
を定義します。
template<typename L, typename R> struct plus{ template<typename... Args> auto operator ()(Args... args) const{ return left(args...) + right(args...); } L left; R right; }; template<typename L, typename R> plus<L, R> operator +(L left, R right){ return {left, right}; }
まず、operator +
を定義し、その左辺値と右辺値を保持するクラスを定義します。
そして operator ()
ではその『左辺値と右辺値を評価』します。
ここがポイントですね。
つまり
関数オブジェクト + 関数オブジェクト
という式は
関数オブジェクト(引数) + 関数オブジェクト(引数)
という風に評価されます。
このように関数オブジェクトが再帰的に評価されることで arg1
や val
などの結果が最終的には『+
演算子として』処理されます。
はい、ここがポイントですね。
これにより
arg1 + arg2 + val(42) + arg1;
みたいな連続した式も問題なく展開されます。
この時にクラスが保持してる値が『関数オブジェクト』か『そうでない』かがキモになってきます。
今回定義した operator +
は左辺値と右辺値が『関数オブジェクトを受け取ること』を想定しているので『operator ()
』で評価します。
しかし、先ほど定義した val()
の holder
クラスは関数オブジェクトではなくて『値』を保持しているため『評価しないで』そのままの値を返します。
基本的にはこれの応用で他の演算子も定義すればおっけーです。
[完成]
以下が完成形になります。
#include <tuple> #include <iostream> namespace lambda{ template<typename T> struct holder{ auto operator ()(...) const{ return value; } T value; }; template<typename T> holder<T> val(T t){ return {t}; } template<std::size_t Index> struct placeholder{ template<typename... Args> auto operator ()(Args... args) const{ return std::get<Index>(std::make_tuple(args...)); } }; const auto arg1 = placeholder<0>{}; const auto arg2 = placeholder<1>{}; const auto arg3 = placeholder<2>{}; template<typename L, typename R> struct plus{ template<typename... Args> auto operator ()(Args... args) const{ return left(args...) + right(args...); } L left; R right; }; template<typename L, typename R> plus<L, R> operator +(L left, R right){ return {left, right}; } } int main(){ auto expr = lambda::arg1 + lambda::arg2; std::cout << expr(4, 2) << std::endl; auto plus3 = lambda::arg1 + lambda::val(3); std::cout << plus3(4) << std::endl; return 0; }
演算子は ADL で呼び出してるので namespace を追加してます。
とりあえず、これで完成ですね!
Expression Templates という概念はわかりづらいと思いますが、実際はこんな感じにパーツを組み合わせて式をつくっていくと考えればわかりやすいと思います。
C++11/14 でかなり実装も簡単になりましたしね。
また C++11 で言語レベルでラムダ式は追加されましたが、
filter(list, [](auto n){ return n < 0; });
よりも
filter(list, arg1 < val(0));
みたいに今回つくったラムダ式を使ったほうがコードがすっきりすると思います。
これは便利ですね。
ではみなさん、良いお年をー。
[これで終わりと思った?]
残念!まだ終わりではありません!!!
ここからが本番になります。
[C++14 で実装されたと多相ラムダと戻り値型の型推論]
さて、C++14 では新たに引数型を推論する多相ラムダと戻り値型を推論する強力な機能が追加されました。
[多相ラムダ]
ラムダ式の引数型を auto
にすることで関数テンプレートのように型推論を行ないます。
// 引数型に依存することなくラムダ式を定義することができる auto plus = [](auto a, auto b){ return a + b; }; plus(1, 2); // 3 plus(3.14f, 53.24f); // 56.38 plus(3.14f, 42); // 45.14 plus(3.14f, 42); // "homumami"
このように引数型に依存することなくラムダ式を定義することが出来ます。
多相ラムダを受け取ることができる型がほしいよね
[戻り値型の型推論]
C++14 では戻り値型を auto
とすることでコンパイラが式から型推論を行ってくれます。
template<typename T, typename U> auto plus(T a, U b){ // a + b の結果の型を推論してくれる return a + b; } plus(1, 2); // 3 plus(3.14f, 53.24f); // 56.38 plus(3.14f, 42); // 45.14 plus(3.14f, 42); // "homumami"
C++14 以前では戻り値型 decltype()
などを使用する必要がありましたが、C++14 ではそれすら必要なくなりました。
// C++11 時代は decltype() で戻り値型を定義する必要があった template<typename T, typename U> auto plus(T a, U b) ->decltype(a + b){ // a + b の結果の型を推論してくれる return a + b; }
decltype()
を使用した場合は『関数を定義するときに型が決定されてる必要』があったんですが、これにより『関数内で定義された型』も返すことができるようになりました。
// 関数内で定義したローカルクラスを返すこともできる auto make_homu(){ struct person{ std::string name; int age; }; return person{ "homu", 14 }; } auto data = make_homu(); data.name; // "homu" data.age; // 14
ただし、次のように『実行時によって異なる型を返す』ことは出来ないので注意してください。
auto func(int flag){ // Error: 同じ型を返さなければならない if( flag ){ return 0; } else { return "Error"; } }
[つまり]
もうお気づきの方もいると思いますが、これにより『ラムダ式』をそのまま返すことができるようになりました。
// C++ でクロージャっぽいものを定義 auto counter(){ int value = 0; // キャプチャした変数を書き換える場合は mutable を付属させる return [=]() mutable{ value += 1; return value; }; } count(); // 1 count(); // 2 count(); // 3
と、いうことで先ほどつくったプレースホルダなどをラムダ式を使って書き換えていきましょう
[val()
定義する 2]
まずは val
から。
これは引数をそのまま返すラムダ式を定義するだけなので簡単ですね。
template<typename T> auto val(T t){ return [=](...){ return t; }; }
はい、先ほど定義した holder
クラスは綺麗サッパリなくなって関数1つだけになりました。
すごいすっきりしましたね。
[プレースホルダオブジェクトを定義する 2]
続けてプレースホルダも定義してみます。
template<std::size_t Index> auto placeholder(){ return [](auto... args){ return std::get<Index>(std::make_tuple(args...)); }; } const auto arg1 = placeholder<0>(); const auto arg2 = placeholder<1>(); const auto arg3 = placeholder<2>();
はい、この通り placeholder
クラスではなくて placeholder
関数になりました。
また、多相ラムダを使用することで template
も省略出来ます。
こちらも実装がすっきりしましたね。
[operator+
を定義 2]
最後に operator +
ですね。
これも簡単に定義できます。
template<typename Left, typename Right> auto operator +(Left left, Right right){ return [=](auto... args){ return left(args...) + right(args...); }; }
完璧ですね…。
[まとめ]
と、言うことでまとめるとこんなコードになります。
#include <tuple> namespace lambda{ template<typename T> auto val(T t){ return [=](...){ return t; }; } template<std::size_t Index> auto placeholder(){ return [](auto... args){ return std::get<Index>(std::make_tuple(args...)); }; } const auto arg1 = placeholder<0>(); const auto arg2 = placeholder<1>(); const auto arg3 = placeholder<2>(); template<typename Left, typename Right> auto operator +(Left left, Right right){ return [=](auto... args){ return left(args...) + right(args...); }; } } // namespace lambda #include <iostream> int main(){ auto expr = lambda::arg1 + lambda::arg2; std::cout << expr(4, 2) << std::endl; auto plus3 = lambda::arg1 + lambda::val(3); std::cout << plus3(4) << std::endl; return 0; }
どうでしょう。
最初にクラスを使って実装したコードに比べてだいぶすっきりしたと思いませんか?
Expression Templates の話どこに行ったんだって感じですよね。
まあでも結果的に C++14 だとこんなコードが書けるようになるんですよ。
面白いですよね。
ちなみに C++14 + ラムダ式で実装した場合の欠点としては『constexpr
』として定義できないことです。
最初にクラスを使って実装した場合は戻り値型に constexpr
をつければコンパイル時に評価することが出来ます。
これは大変強力なのでどっちで実装するべきなのかは悩みどころですね…。
ただ、C++17 ではラムダ式が constexpr
が定義できるらしいのでそこもカバーできるようになると思います。
と、言うことで本記事は『Expression Templates を解説する』記事でもなく『オレオレ Boost.Lambda/Phoenix を実装する』記事でもなく『C++14 の多相ラムダと戻り値型の auto
が素晴らしい』という記事になります。
それではメリークリスマス。
【C++ Advent Calendar 2016 22日目】C++ で enable_if を使うコードのベストプラクティス
[追記]
@pink_bangbi typename std::enable_if<..., std::nullptr_t>::type* = nullptr の * は余計ではありませんか。これだと nullptr_t にする意味が
— akinomyoga (@akinomyoga) 2016年12月22日
すみませんすみません、修正しました。
[本編]
C++ Advent Calendar 201622日目の記事です。
『ベストプラクティス』と書いていますが、釣りタイトルですです。誰かもっといい書き方教えてください。
最近さっぱり C++ を書いてないのでいろいろと厳しい。
あ、あと最初に書いておくと本記事はあくまでも『enable_if
の使い方』に関する記事なので SFINAE 自体にはあまり言及しません。
[enable_if とは]
enable_if
は一般的にSFINAE を利用したコンパイル条件分岐で使用するためのメタ関数になります。
SFINAE とは cpprefjp から引用すると
「SFINAE (Substitution Failure Is Not An Errorの略称、スフィネェと読む)」は、テンプレートの置き換えに失敗した際に、即時にコンパイルエラーとはせず、置き換えに失敗した関数をオーバーロード解決の候補から除外するという言語機能である。
たとえば、関数のシグニチャの一部として「typename T::value_type」が書いてあり、型Tがvalue_typeという型を持っていない場合、その関数がオーバーロード解決から除外される。これによって型が任意の機能を持っているかを、コンパイル時に判定できた。
参照:任意の式によるSFINAE - cpprefjp C++日本語リファレンス
とのこと。
例えば、関数テンプレートの呼び出しの際にテンプレート引数の型が『整数型かそうでないか』で呼び出す関数を切り分けたい場合などで SFINAE と enable_if
を組み合わせて使用します。
まあ C++ を書いてる人であれば enable_if
や SFINAE などは知っていて当然ですよね。
それでは実際に enable_if
を使ったコードを書いていきます。
[C++03 時代の enable_if
]
まず、C++03 時代の enable_if
の利用方法を書いてみましょう。
C++03 ではまだ enable_if
は標準ライブラリに入っておらず、Boost を利用します。
#include <boost/core/enable_if.hpp> #include <boost/type_traits/is_integral.hpp> template<typename T> void func(T t, typename boost::enable_if<boost::is_integral<T> >::type* = 0){ std::cout << t << "は整数だよ" << std::endl; } void func(...){ std::cout << "引数は整数じゃないよ" << std::endl; } func(10); // 10は整数だよ func('c'); // cは整数だよ func(3.14f); // 引数は整数じゃないよ
http://melpon.org/wandbox/permlink/w3oZ2lSl1aS8bbTa
C++03 時代では割とオーソドックな書き方になるかと思います。
C++03 では『関数のデフォルト引数として enable_if::type
を定義してる』のが肝になります。
また『テンプレート型の条件』には is_integral
というメタ関数を使用しています。
このメタ関数は『整数型であれば is_integral::value
が true
を返し、そうでないなら false
』という関数になります。
このメタ関数と enable_if
を組み合わせることで関数呼び出しの条件分岐を行っています。
ちなみに boost::enable_if
の場合は内部で ::value
が呼び出されるので、呼び出し側では ::value
をつける必要はないので『生の型』をそのまま enable_if
へと渡します。
[C++11 時代の enable_if
]
それでは C++03 のコードをベースに C++11 に対応していきましょう。
[std::enable_if
を使う]
C++11 では標準ライブラリに enable_if
が入ったため、Boost に依存することなく enable_if
が使用できるようになりました。
ついでに boost::is_integral
も標準ライブラリ入りしたのでそれも使用するように変更します。
typename boost::enable_if<boost::is_integral<T> >::type* = 0
↓
typename std::enable_if<std::is_integral<T>::value>::type* = 0
ここで注意なのは boost::enable_if
の場合は内部で ::value
が展開されていたんですが、std::enable_if
の場合は型ではなくて値(::value
)を渡す必要があります。
boost::enable_if
から std::enable_if
にコードを変更する場合にはこのことに注意してください。
[関数テンプレートのデフォルト引数で enable_if
を定義]
C++11 では関数テンプレートでも『テンプレート型のデフォルト引数』を定義できるようになりました。
ですので、関数の引数ではなくて『関数テンプレートのデフォルト引数』で enable_if
を定義する事が出来ます。
template< typename T, // nullptr を受け取るのがポイント typename std::enable_if<std::is_integral<T>::value, std::nullptr_t>::type = nullptr > void func(T t){ std::cout << t << "は整数だよ" << std::endl; }
基本的に関数の引数で定義してた時と同じような感じですが、0
ではなくて nullptr
をデフォルト値として渡しています。
また、enable_if
の第二引数は ::type
で返す方を指定できるため std::nullptr_t
を渡してします。
これがポイントなんですが nullptr
で受け取る理由は以下の記事を参照してください。
この時に enable_if
の第二引数に std::nullptr_t
型を渡しているのもポイントなんですが、個人的には
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr
みたいに省略しても基本的には問題ないと思います。
また、他には enabler
というイディオムを利用することもあります。
// enable_if で使用するためのダミーの変数を『宣言』しておく extern void* enabler; template< typename T, // nullptr の変わりに enabler を渡す typename std::enable_if<std::is_integral<T>::value>::type*& = enabler > void func(T t){ std::cout << t << "は整数だよ" << std::endl; }
[C++11 のまとめ]
#include <utility> #include <iostream> template< typename T, typename std::enable_if<std::is_integral<T>::value, std::nullptr_t>::type = nullptr > void func(T t){ std::cout << t << "は整数だよ" << std::endl; } void func(...){ std::cout << "引数は整数じゃないよ" << std::endl; } int main(){ func(10); // 10は整数だよ func('c'); // cは整数だよ func(3.14f); // 引数は整数じゃないよ return 0; }
http://melpon.org/wandbox/permlink/gTyeZk1qGdyYogV5
[C++14 時代の enable_if
]
C++14 ではさらに enable_if
の alias templates である enable_if_t
というメタ関数が標準ライブラリに追加されました。
この enable_if_t
を使用することで typename
と ::type
を省略する事ができるようになります。
typename std::enable_if<std::is_integral<T>::value, std::nullptr_t>::type = nullptr
↓
std::enable_if_t<std::is_integral<T>::value, std::nullptr_t> = nullptr
また、alias templates 自体は C++11 で実装された機能なので、以下のように定義しておけば C++11 でも使用することが出来ます。
template< bool B, class T = void > using enable_if_t = typename enable_if<B,T>::type;
参照:std::enable_if - cppreference.com
[C++17 時代の enable_if
]
よくわからんので誰か書いて。
[まとめ]
まとめると最新版の enable_if
を使ったコードはこうじゃ。
#include <type_traits> #include <iostream> template< typename T, std::enable_if_t<std::is_integral<T>::value, std::nullptr_t> = nullptr > void func(T t){ std::cout << t << "は整数だよ" << std::endl; } void func(...){ std::cout << "引数は整数じゃないよ" << std::endl; } int main(){ func(10); // 10は整数だよ func('c'); // cは整数だよ func(3.14f); // 引数は整数じゃないよ return 0; }
http://melpon.org/wandbox/permlink/7WmFlkGJQ94yVRAU
と、言う感じで各時代の enable_if
の使い方をまとめてみました。
enable_if
を使った SFINAE はメタプログラミングをする C++er に取っては一般的なテクニックだと思いますが、結構時代によって書き方が変わってきていますね。
今回は enable_if
の記事だったのであまり SFINAE には突っ込みませんでしたが、SFINAE を使ったテクニックも時代によって変わってきているので、気になる方は調べてみるとよいと思います。
また、他に『enable_if
はもっとこうしたほうが簡単に書けるよ!』みたいなツッコミがあればぜひ教えてもらえると助かります。
あと C++17 はよくわからないので誰か書いてください。 C++17 だと constexpr if があるのでそもそも enable_if
が要らなくなるという話もある
[おまけ]
ちなみに enable_if
の第二引数には ::type
が返す型を指定することが出来ます(デフォルトでは void
型を返す。
ですので関数定義時に戻り値型が決まっているなら次のように『戻り値型』に enable_if
を定義することもできます。
template< typename T > // 戻り値型が void ならそのまま enable_if::type を戻り値型に typename std::enable_if<std::is_integral<T>::value>::type func(T t){ std::cout << t << "は整数だよ" << std::endl; } template< typename T > // 任意の型を返す場合は enable_if の第二引数に渡す typename std::enable_if<std::is_integral<T>::value, int>::type plus10(T t){ return t + 10; } template< typename T > // もちろん enable_if_t を使用して typename と ::type を省略することも std::enable_if_t<std::is_integral<T>::value, T> twice(T t){ return t + t; }
これは C++03 でも有効なのでデフォルト引数で定義したくない方は使ってみるとよいと思います。
unite-oldfiles つくった
【初心者C++er Advent Calendar 2016 13日目】予約済み識別子に注意しよう
初心者C++er Advent Calendar 2016の13日目が空いてるようなの書きました。
本記事は初心者C++er Advent Calendar 201613日目の記事になります。
初心者C++er Advent Calendar 2016では 3回目の記事になりますが、今回は予約済み識別子の話です。
[予約済み識別子とは]
『予約済み識別子』とは『言語仕様』や『処理系』などで使用するために予約されている識別子のことです。
C++ では以下の3つのいずれかを満たす名前が『予約済み識別子』として予約されています。
- グローバルスコープを持ち、
_
で始まる名前 _
で始まり、その次が大文字の名前__
を含む名前
例えば、__LINE__
みたいな定義済みマクロの名前であったり、処理系依存であれば msvc の _MSC_VER
定義済みマクロがそれにあたります。
要は『__
を含む名前とかはコンパイラやライブラリの実装で使う可能性があるからユーザは使わないでねー』というためのものですね。
ですので、以下のようなコードは未定義の動作になります。
// インクルードガードなどで名前の先頭に _ をつけてるのでダメ #ifndef _INCLUDED_PATH_TO_ #define _INCLUDED_PATH_TO_ #endif // _ から始まってる名前なのでダメ typedef struct _X { // __ が含まれてる名前なのでダメ int value__; } X;
インクルードガードのマクロ名に『_
から始まってる名前』を使ってるコードをよく見かけますが、これも厳密にいえば未定義の動作になります。
[まとめ]
実際のところ予約済み識別子を使ったところでそこまで問題になる可能性は低いと思います。
しかし、わざわざ未定義の動作になる可能性があるコードを書く必要もないので、C++ を書くときは注意するとよいと思います。
言語によっては _value
みたいに『先頭に _
をつける事』を推奨してることもありますが、C++ では基本的に避けるべきです。
ですので、C++ では以下のように『名前の末尾に _
をつけること』が多いです。
// だめ auto _value = 42; // おっけー auto value_ = 42;
こんな感じで基本的には名前の先頭に _
をつけるのを避けるように気をつければ問題ないと思います。
以上で、予約済み識別子の説明は終了です。
予約済み識別子警察には気をつけましょう。
[余談]
Vim を使ってる人は以下のプラグインをいれてみるといいと思います。
[参照]
RSpec のマッチャで === を使う
通常 eq
を使用すると ==
が使われるんですが、これを ===
で比較したかった。
このように任意の演算子でテストしたい場合は次のように書ける。
expect(9).to be > 6 expect(3).to be <= 3 expect(1).to be < 6 expect('a').to be < 'b' expect(String).to be === "hoge"
これは知らなかったので便利。
[参照]
【初心者C++er Advent Calendar 2016 8日目】関数テンプレートと関数オブジェクト
タイトルは釣りです。
初心者C++er Advent Calendar 2016 8日目の記事になります。
8日が空いていたので簡単なものを書きます。
前回の記事を書いたあとに
(省略されたtemplateのが気になった)
— Sd2E 002/100 (@chiwawa_star) 2016年12月6日
と、言われたので今回はその捕捉になります。
[関数テンプレート]
前回『残念ながら関数の引数型で auto を使用して型推論を行うことは出来ません』と書きました。
確かに auto
を使用して型推論を行うことは出来ませんが、template
を使用して型推論を行うことは出来ます。
template<typename A, typename B> auto plus(A a, B b){ return a + b; } plus(1, 2); // int + int = int plus(1, 3.14f); // int + float = float plus(std::string("homu"), std::string("mami")); // std::string + std::string = std::string
こんな感じに auto
ではなくて template
を使用して引数型を推論する形になります。
基本的な挙動はラムダ式と同じになります。
[関数テンプレートの問題点]
これだけじゃ味気ないのでもうちょっと突っ込んで関数テンプレートの場合の問題点を上げます。
さて、次のように std::for_each
とラムダ式を利用して値を出力することが出来ます。
auto output = [](auto x){ std::cout << x << std::endl; }; std::vector<int> xs = {1, 2, 3, 4, 5}; std::for_each(std::begin(xs), std::end(xs), output);
しかし、これを関数テンプレートで行おうとするとエラーになります。
template<typename T> void output(T t){ std::cout << t << std::endl; } std::vector<int> xs = {1, 2, 3, 4, 5}; // error: no matching function for call to 'for_each' std::for_each(std::begin(xs), std::end(xs), output);
これは関数テンプレートを値として使用する場合に『テンプレートの型を明示する』必要があるためです。
ですので、関数などに関数テンプレートを渡す場合は次のようにテンプレートの型を明示する必要があります。
template<typename T> void output(T t){ std::cout << t << std::endl; } std::vector<int> xs = {1, 2, 3, 4, 5}; // output<int> を明示する std::for_each(std::begin(xs), std::end(xs), output<int>);
これで解決します。
[関数オブジェクトを使用する]
さて、関数テンプレートのテンプレート型を明示化することで解決しましたが、毎回型を記述するのは手間です。
そこで、関数ではなくてクラスを利用して回避します。
// operator () を定義したクラスを定義する struct output_{ template<typename T> void operator ()(T t) const{ std::cout << t << std::endl; } }; // そのクラスのオブジェクトを用意する output_ const output; std::vector<int> xs = {1, 2, 3, 4, 5}; // あたかも関数のように振る舞うオブジェクト std::for_each(std::begin(xs), std::end(xs), output);
関数ではなくてメンバ関数として定義することで型を明示化しなくてもよいようになります。
また、このように operator ()
を定義して関数のように振る舞うオブジェクトのことを一般的に関数オブジェクトと呼びます。
[まとめ]
よく『template
は難しい』みたいなことを聞きますが、やってることは単なる型推論なだけなので auto
を理解してるのであれば template
に置き換えるだけなのでそんなに難しくはないと思います。
auto
にしてもそうですが template
を使用することでどんどん型による依存を減らして行くことが可能になります。
【初心者C++er Advent Calendar 2016 3日目】C++ で型推論(auto)しよう
初心者C++er Advent Calendar 2016 3日目の記事です。
3日目が空いてるようなので書いてみました。
今回は初心者向けの Advent Calendar ということで軽めの記事になります。
また、本記事では C++14 を基準としたコードになります。
[C++ で型を書く場面]
さて、C++ は静的型付言語なので、いろんな場面で int
や float
、std::string
などと言った型を書くことが多いです。
// 関数の戻り値型や引数型 int func(std::string, float); // 変数の宣言時に型を指定する int a = func(std::string("homu"), 3.14f);
今回はこれらの型を auto
を使用して省略できる場面を紹介したいと思います。
[変数の宣言時に auto
を使う]
変数の宣言時に auto
を使用することで右辺値から型推論をしてコンパイラが自動的に型を割り当てます。
// 代入式の右辺値である 42 からコンパイラが型を推論して自動的に型を割り当てる // 42 は int 型なので n は int 型になる auto n = 42; // f は float 型である auto f = 3.14f; std::vector<int> v; // it は std::vector<int>::iterator 型になる auto it = std::begin(v);
このように本来は
std::vector<int>::iterator it = std::begin(v);
というように型が長くなってしまう場合、auto
を利用することで
auto it = std::begin(v);
という風に簡略化することが出来ます。
また、当然ですがこの場合は『変数の初期値』を指定する必要があります。
[戻り値型を auto
にする]
関数の戻り値型を auto
にすることで return
文などからコンパイラが自動的に戻り値型を推論します。
// この関数の戻り値型は int 型 + float 型の結果(float 型)になる auto plus(int a, float b){ return a + b; } // 複数の return をする場合は『同一の型』を return する必要がある auto func(int flag){ if( flag ){ return std::string("OK"); } else { // コンパイル時に型を決定する必要があるので違う型を return することは出来ない // return -1 return std::string("NG"); } } // 戻り値を省略した場合は void 型になる auto hello_world(){ std::cout << "hello, world" << std::endl; // こっちでもおk // return; }
このように戻り値型に auto
を使用することが出来ます。
注意点としては必ず『同じ型の値を return する』ということですね。
また、残念ながら関数の引数型で auto
を使用して型推論を行うことは出来ません。
[ラムダ式の引数で auto
を使用する]
先ほど関数の引数では auto
は使えないと書きましたが、実はラムダ式の引数であれば auto
を使用する事が出来ます。
// 引数型を auto にすることでどんな型でも受け取ることができる auto output = [](auto a) { std::cout << a << std::endl; }; output(42); output("homu"); // std::vector<int> も渡すことはできるが、std::cout に対応してないのでエラー // output(std::vector<int>{}); // 戻り値型の auto と組み合わせて使用することもできる // この場合、戻り値型は引数型に依存する auto plus = [](auto a, auto b) -> auto { return a + b; }; plus(1, 2); // int + int = int plus(1, 3.14f); // int + float = float plus(std::string("homu"), std::string("mami")); // std::string + std::string = std::string
ラムダ式の場合、引数型によって戻り値型が変わるというのが面白いですね。
C++ では
[まとめ]
C++ では複雑な型を扱うことが多いのですが、このように auto
を使用することで冗長なコードをすっきり記述できる場面が多いです。
また、今回は使用しませんでしたが template を使用することで更に型を省略することも出来ます。
ただ、C++ では『型について理解することも重要』なので初心者の方はまず最初に『どんな型として扱わるのか』と考えてみましょう。
この関数の戻り値型は何か、式を演算した結果何型を返すのか、暗黙の型変換とは etc...と C++ で型について考えることは尽きません。
その上で型に慣れてきたら auto
を使ってみるとよいと思います。
そんな感じでざっくりとして内容ですが 初心者C++er Advent Calendar 2016 3日目の記事になります。