C++ で一般化された on を書く
Data.Function
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c on f g = \x y -> f (g x) (g y)
これの引数の数と型を一般化したものを,C++ を使っていくつかの方法で書いてみました.Variadic な関数オブジェクトを書く上で参考になるかもしれません.
まず C++0x の機能を使ったものから.
#include <utility> #include <type_traits> namespace on_detail { template <class F, class G> struct result { F m_f; G m_g; // sorry, unimplemented: use of 'type_pack_expansion' in template /* template <class ...Args> auto operator()(Args &&...args) const -> decltype(m_f(m_g(std::forward<Args>(args))...)) { return m_f(m_g(std::forward<Args>(args))...); } */ template <class A, class = void> struct result_of_g {}; template <class A> struct result_of_g<A, decltype(std::declval<G const &>()(std::declval<A>()), void())> { typedef decltype(std::declval<G const &>()(std::declval<A>())) type; }; template <class ...Args> auto operator()(Args &&...args) const -> decltype(m_f(std::declval<typename result_of_g<Args>::type>()...)) { return m_f(m_g(std::forward<Args>(args))...); } }; } template <class F, class G> inline auto on(F &&f, G &&g) -> on_detail::result<typename std::decay<F>::type, typename std::decay<G>::type> { return { std::forward<F>(f), std::forward<G>(g) }; }
g++ の "sorry, unimplemented" に引っかかったので回り道をしていますが,本質的には variadic templates を使って仕様を書き下しているだけです.C++0x の素晴らしさが伝わってきます.
次に C++03 での実装を考えましょう.可変長引数は,オーバーロードを並べ立てることでエミュレートできます.そう,Boost.PP です.また,関数オブジェクトを作る際には,Boost.Egg が非常に有用です.これらのライブラリを贅沢に使って実装します.
#include <boost/utility/result_of.hpp> #include <boost/preprocessor/repetition/enum_binary_params.hpp> #include <boost/preprocessor/repetition/enum_params.hpp> #include <boost/preprocessor/repetition/enum.hpp> #include <boost/preprocessor/arithmetic/inc.hpp> #include <boost/preprocessor/facilities/intercept.hpp> #include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/iteration/local.hpp> #include <boost/egg/function.hpp> #include <boost/egg/generator.hpp> #include <boost/egg/construct_braced2.hpp> #include <boost/mpl/placeholders.hpp> namespace on_detail { template <class F, class G> struct little_result { F m_f; G m_g; typedef typename boost::result_of<F const()>::type nullary_result_type; template <class Re> Re call() const { return m_f(); } template <class Me, BOOST_PP_ENUM_BINARY_PARAMS(BOOST_PP_INC(BOOST_EGG_MAX_ARITY), class A, = void BOOST_PP_INTERCEPT)> struct apply; #define ON_apply_g_to_args(z, i, unused) typename boost::result_of<G const(BOOST_PP_CAT(A, i) &)>::type #define ON_call_g_with_args(z, i, unused) m_g(BOOST_PP_CAT(a, i)) #define BOOST_PP_LOCAL_MACRO(n) \ template <class Me, BOOST_PP_ENUM_PARAMS(n, class A)> \ struct apply<Me, BOOST_PP_ENUM_PARAMS(n, A)> \ : boost::result_of<F const(BOOST_PP_ENUM(n, ON_apply_g_to_args, ~))> \ {}; \ template <class Re, BOOST_PP_ENUM_PARAMS(n, class A)> \ Re call(BOOST_PP_ENUM_BINARY_PARAMS(n, A, &a)) const \ { \ return m_f(BOOST_PP_ENUM(n, ON_call_g_with_args, ~)); \ } \ /**/ #define BOOST_PP_LOCAL_LIMITS (1, BOOST_EGG_MAX_ARITY) #include BOOST_PP_LOCAL_ITERATE() #undef ON_call_g_with_args #undef ON_apply_g_to_args }; } template <class F, class G> struct result_of_on { typedef boost::egg::function<on_detail::little_result<F, G> > type; }; typedef boost::egg::generator< result_of_on< boost::egg::deduce<boost::mpl::_1, boost::egg::as_value>, boost::egg::deduce<boost::mpl::_2, boost::egg::as_value> >::type, boost::use_default, boost::egg::X_construct_braced2<> >::type T_on; T_on const on = BOOST_EGG_GENERATOR();
コードの説明はしません.ただし,variadic な関数オブジェクトを書く際にはしばしば,引数を取らない場合の戻り値に関する問題が発生し (参照),Egg はその良い解決策となることを指摘しておきます.
参考までに,この on を中置記法で使う例を紹介しておきましょう.
#include <vector> #include <utility> #include <string> #include <boost/range/algorithm/sort.hpp> #include <boost/egg/infix.hpp> #include <boost/egg/functional.hpp> #include <boost/egg/get.hpp> #include "./on.hpp" int main() { namespace egg = boost::egg; using namespace boost::egg::infix; std::vector<std::pair<std::string, int> > v; // ... boost::sort(v, egg::less ^on^ egg::X_get_c<0>()); // pair の最初の要素でソートする }
さて,上の実装は悪くありませんが,PP を使っているためデバッグしにくい,あるいは Boost の一部でない Egg を使っている,などの問題点もあります.そこで,Fusion を使った実装も示しておきましょう.
#include <boost/utility/result_of.hpp> #include <boost/fusion/include/fused.hpp> #include <boost/fusion/include/transform.hpp> #include <boost/fusion/include/unfused.hpp> #include <boost/type_traits/remove_reference.hpp> #include <boost/functional/forward_adapter.hpp> namespace on_detail { template <class F, class G> struct fused_result { F m_f; G m_g; fused_result(F f, G g) : m_f(f), m_g(g) {} template <class> struct result; template <class Me, class Args> struct result<Me(Args)> : boost::result_of< boost::fusion::fused<F>( typename boost::fusion::result_of::transform< typename boost::remove_reference<Args>::type, G >::type ) > {}; template <class Args> typename result<fused_result const(Args &)>::type operator()(Args &args) const { return boost::fusion::fused<F>(m_f)(boost::fusion::transform(args, m_g)); } }; } namespace result_of { template <class F, class G> struct on { typedef boost::forward_adapter< boost::fusion::unfused<on_detail::fused_result<F, G> > > type; }; } template <class F, class G> inline typename result_of::on<F, G>::type on(F f, G g) { return boost::fusion::unfused<on_detail::fused_result<F, G> >(on_detail::fused_result<F, G>(f, g)); }
fused / unfused を使うことにより,複数の引数を受け取るコードではなく,それらをパックした 1 つの fusion シーケンスを受け取るコードを書いて実装することができます.ただし,unfused は lvalue しか受け取ることができないので,forward_adapter を挟んでやる必要があります.