テンプレートの特殊化と継承に関する優れた実践

テンプレートの特殊化は、継承階層を考慮しません。たとえば、 Base のテンプレートを特殊化して Derived でインスタンス化すると、特殊化は選択されません(下記のコード(1)を参照)。

これはLiskov置換原則に違反することがあるため、大きな障害となります。たとえば、この質問に取り組んでいるとき、私は stdでBoost.Rangeアルゴリズムを使用できないことに気付きました: :sub_match を実行していますが、 std :: pair を使用することができます。 sub_match pair から継承しているため、 pair が使用されているところでは、これは、テンプレートの特殊化を使用する特性クラスのために失敗します。

この問題は、 enable_if is_base_of (コード(2)を参照)とともに部分テンプレートの特殊化を使用することで解決できます。特にライブラリコードを書くときに、私は完全な専門化を超えてこの解決法を常に支持すべきでしょうか?このアプローチには私が監督している欠点がありますか?それはあなたが頻繁に使用した、またはしばしば使用されていることが実践ですか?


サンプルコード

(1)
#include 

struct Base {};
struct Derived : public Base {};

template < typename T >
struct Foo
{
    static void f() { std::cout << "Default" << std::endl; }
};

template <>
struct Foo< Base >
{
    static void f() { std::cout << "Base" << std::endl; }
};

int main()
{
    Foo::f();//prints "Default"
}

(2)
#include 
#include 

struct Base {};
struct Derived : public Base {};

template 
struct Foo
{
    static void f() { std::cout << "Default" << std::endl; }
};

template 
struct Foo<
    T, typename 
    std::enable_if< std::is_base_of< Base, T >::value >::type
>
{
    static void f() { std::cout << "Base" << std::endl; }
};

int main()
{
    Foo::f();//prints "Base"
}
23
+1。確かに素晴らしい質問です。
追加された 著者 Nawaz,
+1優れた質問とモチベーション。
追加された 著者 sehe,

1 答え

enable_if is more flexible

私はあなたが本当にenable_ifアプローチを好むべきだと思います:それはあなたが必要とすることができるすべてを可能にします。

例えば。派生クラスがベースに対してLiskov-Subsitutableである可能性があります(例えば、ベースがPODクラスであるために、あなたが[適用できない] /適用したくない] しかし一方、DerivedはPOD以外の振る舞いやクラス構成とは完全に直交するようなものである)。

enable_if gives you the power to define exactly the conditions.

ハイブリッドアプローチ

また、汎用特性からアプリケーション固有の特性を導出する特性クラスを実装することで、いくつかのミドルグラウンドを達成することもできます。 「カスタム」特性は、可能な限り多形的に形質を適用するために、enable_ifおよびメタプログラミング技術を使用することができる。そうすることで、実際の実装では複雑なenable_if/dispatchダンスを繰り返す必要はなく、(複雑さを隠す)custom-traitsクラスを単純に消費することができます。

I think some (many?) Boost libraries use the ハイブリッドアプローチ (I've seen it in some capacity where it bridges e.g. fusion/mpl, I think also various iterator traits in Spirit).

私は個人的には、ライブラリのコアビジネスから '配管工事'を効果的に分離し、メンテナンスとドキュメンテーション(<!

13
追加された
@sehe:例:あなたはstd :: enable_if :: value> :: typeの代わりにmytraits :: eligible_for_this_specialization :: typeのようなものを好むでしょうか?
追加された 著者 user396672,
@ user396672:いいえ、私はむしろenable_ifを避け、 mytraits を直接使用したいと思います。カスタムtraitクラスの中で条件(おそらく enable_if に基づいて)を '非表示'にすることができます。
追加された 著者 sehe,
私は、図書館の設計者は、すべての可能性のある図書館のユーザーのために前もって決定することはできないと考えています。 (例えば、 std :: pair の範囲の例のような境界の場合がありますが、一般に、ライブラリの設計者は、 is std :: pair <...> は範囲をモデル化する必要があります
非常に良い答えは、ありがとう。 タグ送信に関連したハイブリッドアプローチが考えられます。タグを取得するためのポリモーフィックなtraitクラス、タグに基づいて特殊化やオーバーロードを使用することができます(ただし、タグは多形であることもあるので、おそらくオーバーロード用に予約する必要があります)。
追加された 著者 Luc Touraille,
@ user396672:このすべてを考える方法の1つは、基本テンプレートを、特性クラスを使用してカスタマイズ可能なフックを提供する「テンプレートメソッド」(設計パターン)として表示することです。基本テンプレートの一部をカスタマイズする必要がある場合、対応する特性を特殊化(「上書き」)する必要があります。全体の構造/振る舞いを変更する必要がある場合でも、基本テンプレートを特殊化(「上書き」)することができます。
追加された 著者 Luc Touraille,
私は、特殊化の対象となる可能性があるすべてのものを(おそらく多形的に特殊化される)カプセル化し、ユーザーに基本テンプレートではなく特性を特化させることをお勧めします(テンプレートメソッドは通常は仮想ではない)。
追加された 著者 Luc Touraille,
ユーザの便宜のために、すでにライブラリによって提供されている形質特化は多分多型であるべきです。
追加された 著者 Luc Touraille,
私はBoost.Range開発者を非難しません、 std :: pair はとにかく派生するものではありません:)。
追加された 著者 Luc Touraille,