読者です 読者をやめる 読者になる 読者になる

部分特殊化には、テンプレートパラメータ自身を使わなければならない

C++

Boost.GeometryのFusionアダプトを作ってるときに嵌った問題。


Fusionシーケンスとしてアダプトされた全ての型をGeometryのConceptとして扱えるようにするために、いくつかのメタ関数をFusionシーケンスで特殊化する必要がありました。しかし残念ながら、部分特殊化には制限がありました。


以下のように、特殊化の構文でenable_ifを使ってテンプレートパラメータの型がFusionシーケンスかどうかを判断し、enable_ifの結果の型で特殊化する、という書き方はできません。

#include <boost/fusion/support/is_sequence.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/utility/enable_if.hpp>

template <class T>
struct X {
    static const int value = false;
};

template <class Seq>
struct X<
    typename boost::enable_if<
        boost::fusion::traits::is_sequence<Seq>,
        Seq
    >::type // 戻り値の型はSeq
> {
    static const int value = true;
};

int main()
{
    static_assert(X<boost::fusion::vector<int, char> >::value, "not fusion sequence");
}
main.cpp:11:8: error: template parameters not used in partial specialization:
cpp/main.cpp:11:8: error: 'Seq'

テンプレートパラメータが部分特殊化で使われていない、というふうに言われます。


Xのプライマリテンプレートが使えればこの問題は容易に解決できるのですが、そんなことをするとFusionシーケンスがデフォルトでGeometryのConceptになるという謎の仕様になってしまいます。


そこで回避策として、メタ関数にenable_if用のダミーパラメータを持たせる、という方法をとりました。

#include <boost/fusion/support/is_sequence.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/utility/enable_if.hpp>

// Enableはenable_if用ダミーパラメータ
template <class T, class Enable = void>
struct X {
    static const int value = false;
};

template <class Seq>
struct X<
    Seq,
    typename boost::enable_if<
        boost::fusion::traits::is_sequence<Seq>
    >::type // 戻り値の型はvoid
> {
    static const int value = true;
};

int main()
{
    static_assert(X<boost::fusion::vector<int, char> >::value, "not fusion sequence");
}

これでメタ関数Xが、Fusionシーケンスかどうかで機能を分岐できるようになりました。


これを実際に適用した例は、Boost 1.47.0以降のboost/geometry/geometries/adapted/boost_fusion.hppで見ることができます。


これは、今後コンセプトベースのC++ライブラリが現れていくにつれ、よく遭遇する問題となってくるでしょうから、覚えておくと便利です。