C++ (fork) Advent Calendar 2013 : C++ で型推論を活用する
この記事は C++ (fork) Advent Calendar 2013 の 21日目の記事になります。
なんとなく書きたいことがあったので突発的に登録したんですが、周りが割りとガチで戦々恐々としています。
そんな C++ (fork) Advent Calendar 2013 ですが、今回は軽めの記事になります。
[ソース]
#include <string> #include <iostream> #include <vector> #include <typeinfo> #include <boost/lambda/lambda.hpp> #include <boost/function.hpp> #include <boost/range/any_range.hpp> #include <boost/range/adaptor/sliced.hpp> // 戻り値型がわからないのでとりあえず T で… template<typename T, typename U> T plus(T t, U u){ return t + u; } // 戻り値型が Seq に依存するので // any_range を使用 template<typename Seq> boost::any_range< int, boost::forward_traversal_tag, int, std::ptrdiff_t > slice1_4(Seq const& seq){ return seq | boost::adaptors::sliced(1, 4); } int main(){ std::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); // std::vector<int>::iterator を使用する必要がある // std::vector 要素の型が変わればここも変える必要がある for(std::vector<int>::iterator it = v.begin() ; it != v.end() ; it++){ std::cout << *it << ","; } std::cout << "\n"; std::cout << "--------------------------" << std::endl; // slice1_4 の戻り値型は any_range なので // 当然 any_range で受け取る必要がある boost::any_range< int, boost::forward_traversal_tag, int, std::ptrdiff_t > result = slice1_4(v); // ここで使用したい iterator の型はわからない…。 // for(??? it = result.begin() ; it != result.end() ; it++){ // std::cout << *it << ","; // } std::cout << "\n"; std::cout << "--------------------------" << std::endl; namespace lambda = boost::lambda; // Boost.Lambda の関数オブジェクトは boost::function で保持する事が可能 // ただし、シグネチャを定義する必要がある boost::function<int(int, int)> twice_int = lambda::_1 + lambda::_1; std::cout << twice_int(1, 2) << std::endl; // その為、シグネチャが異なる場合は再定義する必要がある boost::function<float(float, float)> twice_float = lambda::_1 + lambda::_1; std::cout << twice_float(-3.25, 1.95) << std::endl; std::cout << "--------------------------" << std::endl; // plus() の戻り値型は第一引数の型なので順番によって結果が異なる…。 std::cout << plus(1, 3.14f) << std::endl; std::cout << plus(3.14f, 1) << std::endl; return 0; }
[出力]
1,2,3,4,5, -------------------------- -------------------------- 2 -6.5 -------------------------- 4 4.14
[wandbox]
http://melpon.org/wandbox/permlink/QGMU4m8G42qYGt3o
さて、上記は C++03 時代には割りとありがちなコードですね。
template を使用すると関数の戻り値型が複雑になる場合が多いです。
そういう時は any_range や function と言った TypeErasure が活用されていたと思います。
[ソース]
#include <string> #include <iostream> #include <vector> #include <typeinfo> #include <boost/lambda/lambda.hpp> #include <boost/function.hpp> #include <boost/range/any_range.hpp> #include <boost/range/adaptor/sliced.hpp> // t + u の結果を戻り値型にする template<typename T, typename U> auto plus(T t, U u) ->decltype(t + u){ return t + u; } // これも auto と decltype を使用 template<typename Seq> auto slice1_4(Seq const& seq) ->decltype(seq | boost::adaptors::sliced(1, 4)){ return seq | boost::adaptors::sliced(1, 4); } int main(){ std::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); // ローカル変数は auto で定義 for(auto it = v.begin() ; it != v.end() ; it++){ std::cout << *it << ","; } std::cout << "\n"; std::cout << "--------------------------" << std::endl; auto result = slice1_4(v); for(auto it = result.begin() ; it != result.end() ; it++){ std::cout << *it << ","; } std::cout << "\n"; std::cout << "--------------------------" << std::endl; namespace lambda = boost::lambda; // 関数のシグネチャを書く必要がないのでどんな型でも受け取る事ができる auto twice = lambda::_1 + lambda::_1; std::cout << twice(1, 2) << std::endl; std::cout << twice(-3.25, 1.95) << std::endl; std::cout << "--------------------------" << std::endl; // 式の結果を戻り値型にしているので // 引数の順番には依存しない std::cout << plus(1, 3.14f) << std::endl; std::cout << plus(3.14f, 1) << std::endl; return 0; }
[出力]
1,2,3,4,5, -------------------------- 2,3,4, -------------------------- 2 -6.5 -------------------------- 4.14 4.14
[wandbox]
http://melpon.org/wandbox/permlink/h0jOoQQvaWImT29m
はい、2つのコードを比べてみるとあきらかにすっきりとして見やすくなっていますね。
auto を使用する事で型を書かずに変数を定義することができます。
特に Boost.Lambda の式を Boost.Funcion を使用することなく保持することができるのが強力ですね。
これで変数の定義時に引数の型を定義する必要がないのでどんな型でも渡す事ができるようになります。
また、auto + decltype を使用して関数の戻り値型も型推論する事が可能です。
// 戻り値型の後方宣言を使用する // こうすることで t + u の結果の型を戻り値型として定義する事ができる template<typename T, typename U> auto plus(T t, U u) ->decltype(t + u){ return t + u; }
ただし、decltype 内で式を定義する必要があるのでコードが重複してしまします。
上記のように短いコードであればさほど気になりませんが、
template<typename Seq> auto remove_even(Seq const& seq) ->decltype(seq | boost::adaptors::filtered(boost::lambda::_1 % 2 != 0)){ return seq | boost::adaptors::filtered(boost::lambda::_1 % 2 != 0); }
これぐらいになると流石に嫌になってきますね。
ちなみにラムダ式では定義が return 文のみであれば decltype を使用することなく戻り値型の型推論を行ってくれます。
auto make_plus_func = [](int n){ return boost::lambda::_1 + boost::lambda::_2 + n; }; auto plus3 = make_plus_func(3); std::cout << plus3(1, 2) << std::endl; // => 6
ラムダベンリヤッター
[C++14 時代]
あれから3年… C++ はパワーアップして返ってきた!
と、いうことで最先端は C++14 らしいです。
C++14 は C++11 の時にもどかしい気持ちでいっぱいだった
関数の戻り値型の型推論
が追加される予定です。
既にいくつかのコンパイラでは実装されているので試してみましょう。
[ソース]
#include <string> #include <iostream> #include <vector> #include <typeinfo> #include <boost/range/adaptor/sliced.hpp> // 戻り値型の後方宣言が必要なく // auto のみで定義 template<typename T, typename U> auto plus(T t, U u){ return t + u; } template<typename Seq> auto slice1_4(Seq&& seq){ return seq | boost::adaptors::sliced(1, 4); } // 型さえ合っていれば複数の return 文を定義してもよい auto is_even(int n){ if( n % 2 == 0 ){ return true; } else{ return false; } } // なのでこういう書き方はコンパイルエラー // auto // is_odd(int n){ // if( n % 2 != 0 ){ // return 1; // } // else{ // return false; // } // } int main(){ std::cout << plus(1, 3.14f) << std::endl; std::cout << plus(3.14f, 1) << std::endl; auto v = std::initializer_list<int>{1, 2, 3, 4, 5}; auto result = slice1_4(v); for(auto&& n : result){ std::cout << n << std::endl; } std::cout << is_even(2) << std::endl; std::cout << is_even(9) << std::endl; return 0; }
[wandbox]
http://melpon.org/wandbox/permlink/NKz7ZqPwrAkllsv3
こんな感じで戻り値型が auto であれば return 文から自動的に戻り値型の型推論が行われます。
template を使用しているとどうしても戻り値型が大変なことになってしまうケースが多かったのでこれでだいぶ改善されますね。
C++14 はよはよ。
[おまけ]
C++14 の仕様を全然把握していないんですけどこんな感じにローカルクラスを関数やラムダ式の戻り値型にするのは仕様的に大丈夫なのだろうか。
特にラムダ式の戻り値型の型推論の条件が緩和されていればだいぶ嬉しいのだけれども。
#include <iostream> #include <typeinfo> auto func(){ // ローカルで定義した型を返す struct X{ int value; }; return X{10}; } auto func2(){ struct X{ float value; }; return X{3.14f}; } int main(){ auto x = func(); auto x2 = func2(); // 別々の型 static_assert(!std::is_same<decltype(x), decltype(x2)>{}, ""); // ラムダでも同様に auto x3 = []{ struct X{ int value; }; return X{42}; }(); std::cout << x.value << std::endl; std::cout << x2.value << std::endl; std::cout << x3.value << std::endl; std::cout << typeid(x).name() << std::endl; std::cout << typeid(x2).name() << std::endl; std::cout << typeid(x3).name() << std::endl; return 0; }
[出力]
10 3.14 42 Z4funcvE1X Z5func2vE1X ZZ4mainENK3$_0clEvE1X