関数テンプレートの特殊化について
関数テンプレートの特殊化いろいろ
なんか日記を書くのを忘れていました。よくない。というわけで、最近おぼえたtipsを書きます。クラステンプレート内のテンプレートの特殊化(もどき)についての自分なりのまとめ。
まず、
namespace A { template<int V> static void YYY() { std::printf("default %d\n", V); } } int main() { YYY<0>(); YYY<128>(); }
のような関数テンプレートがあるとき、これを特殊化*1した関数は
namespace A { template<int V> static void YYY() { std::printf("default %d\n", V); } template<> static void YYY<128>() { std::printf("for 128\n"); } template<> static void YYY<256>() { std::printf("for 256\n"); } }
のように書けます。同様に、メンバ関数テンプレート
struct B { template<int V> static void YYY() { std::printf("default %d\n", V); } };
があった場合、この特殊化は、Bの中、クラススコープには規格上書いてはいけないことになっており、g++も実際にそういうコードを受け付けない*2ので、
template<> inline void B::YYY<128>() { std::printf("for 128\n"); } template<> inline void B::YYY<256>() { std::printf("for 256\n"); }
のように名前空間スコープに書けばOKです。クラス外に出すとインライン化されにくくなるので、inlineをつけておきました。では、外側のクラスがクラステンプレートだった場合はどうかというと、
template <typename T> struct C { template<int V> static void YYY() { std::printf("default %d\n", V); } };
次のように、外側のCを明示的に特殊化した上で内側のYYY()を特殊化するような関数を名前空間スコープに書くことはできるけど、
// C<int> に対する YYY<128>() template<> template<> inline void C<int>::YYY<128>() { std::printf("for 128\n"); }
Cを特殊化しないでYYY()を特殊化するのはg++に "error: enclosing class templates are not explicitly specialized" だと言われてしまいコンパイルできません。C++の規格にも上のコードとそっくりの例まで挙げてダメだと書かれてます*3。
// error: enclosing class templates are not explicitly specialized template<typename T> template<> inline void C<T>::YYY<128>() { std::printf("for 128\n"); }
そういうときどうするかというと、あまり詳しくないですがたぶん、古典ことMC++D, Modern C++ Designの2章に書いてある通り、
- boost::type
; - boost::mpl::bool_<B>
- boost::mpl::int_
- boost::mpl::integral_c
あたりのお好きなもの(LokiでいうType2Type
template <typename T> class C { void YYY_(boost::mpl::int_<128>) { /* specialized */ } void YYY_(...) { /* default */ } public: template<int V> static void YYY() { YYY_(boost::mpl::int_<V>()); } };
こうすると、YYY<0>(); と YYY<128>(); で別々の関数、それぞれ YYY_(...) と YYY_(boost::mpl::int_<128>) が呼ばれることになるので、特殊化が実現できたようなものです。これをここでは特殊化もどきと呼びます。
あるいは、次のように、クラススコープ、特に特殊化されていないクラステンプレートのスコープでも、クラステンプレートの部分特殊化なら可能であることを利用して、D
template <typename T> class D { template <int V, typename Dmy = void> struct D_ { static void XXX() { std::printf("default %d\n", V); } }; template <int V> // partial specialization struct D_<V, typename boost::enable_if_c<V == 128>::type> { static void XXX() { std::printf("for %d\n", V); } }; template <int V> // partial specialization struct D_<V, typename boost::enable_if_c<V == 256>::type> { static void XXX() { std::printf("for %d\n", V); } }; public: template<int V> static void XXX() { D_<V>::XXX(); } };
BoostのMLのこのメールでは、こちらの方法が推奨されていました。次の通り、D_ の部分特殊化版を誰でも後から追加できますからね。
// namespace scope に V==0の場合の特殊化を後から追加 template <typename T> template <int V> struct D<T>::D_<V, typename boost::enable_if_c<V == 0>::type> { static void XXX() { std::printf("for %d\n", V); } };
のように。boost::enable_ifは、これまた数行で書けるだいたいこんな感じのクラステンプレートです。というわけで、「クラステンプレートC内のメンバ関数テンプレートを、Cを特殊化しないで特殊化(もどき)する」には、「クラステンプレートに委譲しとけば?」でファイナルアンサーのように思うのですが、練習(何の?)ため、他の関数に処理を委譲せずに特殊化もどきを実現できないかを考えてみます。
関数テンプレートの enable_ifオーバーロードによる特殊化もどき - 2分岐バージョン
もし、特殊化する関数が1つだけ、Pのときの関数と!Pのときの関数だけ用意すればことたりるなら、boost::enable_ifと関数のオーバーロードを使って、委譲なしで綺麗に特殊化もどきを実現できます。
template<bool B> static void bar(typename boost::enable_if_c<B>::type* = 0) { std::printf("for true\n"); } template<bool B> static void bar(typename boost::disable_if_c<B>::type* = 0) { std::printf("for false\n"); }
としたり、
template<int V> static void boo(typename boost::enable_if_c<V == 0>::type* = 0) { std::printf("for 0\n"); } template<int V> static void boo(typename boost::enable_if_c<V != 0>::type* = 0) { std::printf("default\n"); }
としたり、です。
(おまけ)関数テンプレートの enable_ifオーバーロードによる特殊化もどき - 3+分岐バージョン
ですが、合計3つ以上の関数を用意したい場合にはenable_ifはちょっと面倒です。
template<int V> static void boo(typename boost::enable_if_c<V == 0>::type* = 0) { std::printf("for 0\n"); } template<int V> static void boo(typename boost::enable_if_c<V == 1>::type* = 0) { std::printf("for 1\n"); } static void boo(typename boost::enable_if_c<V == 2>::type* = 0) { std::printf("for 2\n"); } static void boo(typename boost::enable_if_c<!(V == 0) && !(V == 1) && !(V == 2)>::type* = 0) { std::printf("default\n"); }
のようにdefaultな関数の引数がえらいことになります。boo(...) などでdefault関数を書ければいいんですけどね。コンパイラに曖昧だと言われてしまってどうにも。どうにかなるでしょうか。
上の例は、特殊化(もどき)の条件(V==ほげ)が2回づつ登場してしまっているのが気に入りません。ですので、今回は、MPLを使って、頑張って特殊化もどきの条件を各1回だけ書けばいいようにしてみました。はじめてのMPLと。
#include <cstdio> #include <boost/utility/enable_if.hpp> #include <boost/type_traits/is_same.hpp> #include <boost/mpl/int.hpp> #include <boost/mpl/bool.hpp> #include <boost/mpl/lambda.hpp> #include <boost/mpl/list.hpp> #include <boost/mpl/at.hpp> #include <boost/mpl/or.hpp> #include <boost/mpl/fold.hpp> template <typename T> struct X { struct detail { typedef boost::mpl::list< boost::mpl::lambda< boost::is_same< boost::mpl::_1, boost::mpl::int_<128> // V==128で特殊化したい > >::type, boost::mpl::lambda< boost::is_same< boost::mpl::_1, boost::mpl::int_<256> // V==256で特殊化したい > >::type > value_list; // boost::mpl::fold<> にあとで渡す template<int V> struct compar_meta_fn { template<typename Sum, typename Seq> struct apply { typedef boost::mpl::or_< Sum, /* true_ or false_ */ typename Seq::type::template apply<boost::mpl::int_<V> >::type > type; }; }; }; // ::template apply<... という奇妙な文法については、書籍 "C++ Templates - the complete guide" の9章に丁寧な解説があります template<int V> static void XXX(typename boost::enable_if< typename boost::mpl::at_c<typename detail::value_list, 0>::type::template apply<boost::mpl::int_<V> >::type >::type* = 0) { std::printf("for %d\n", V); // 128 } template<int V> static void XXX(typename boost::enable_if< typename boost::mpl::at_c<typename detail::value_list, 1>::type::template apply<boost::mpl::int_<V> >::type >::type* = 0) { std::printf("for %d\n", V); // 256 } template<int V> static void XXX(typename boost::disable_if< typename boost::mpl::fold< typename detail::value_list, boost::mpl::false_, typename boost::mpl::lambda<typename detail::template compar_meta_fn<V> >::type >::type >::type* = 0) { std::printf("default %d\n", V); } }; int main() { X<int>::XXX<0>(); X<long>::XXX<128>(); X<float>::XXX<256>(); X<double>::XXX<512>(); }
以上です。実行結果:
% ./a.out default 0 for 128 for 256 default 512
整数値でなく型で特殊化もどきをしたい場合は、boost::mpl::int_
template <typename T> struct Z { LIST_BEGIN() LIST_ELEM(128), LIST_ELEM(256) LIST_END(lst); template<int V> static void XXX(ENABLE_NTH(lst,0)) { std::printf("for %d\n", V); // 128 } template<int V> static void XXX(ENABLE_NTH(lst,1)) { std::printf("for %d\n", V); // 256 } template<int V> static void XXX(OTHERWISE(lst)) { std::printf("default %d\n", V); } };
くらいに縮めればなんとか使え...ないですかね。とりあえず、異なるVごとに別の XXX
結論
MPLを今更ながら使ってみたら面白かった。なお、MPLを使ってみるにあたっては、稲葉一浩さんのBoost本(第二版)に大変お世話になりました。