2012-07-04 12 views
8

を選んでいません。次のコードではSFINAE:コンパイラは、私が<a href="http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error" rel="noreferrer">SFINAE</a>問題持っている専門的なテンプレートクラス

を、私は「特別」の専門ファンクタと印刷を選択するためにC++コンパイラをしたいが、それは「一般的な印刷です代わりに。

#include <iostream> 
#include <vector> 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename T::Vec> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

特殊構造体が自動的に使用されるように修正するにはどうすればよいですか?メモFunctor構造体をFooに直接的に特化することは望ましくありませんが、Vecタイプのすべてのタイプに特化したいと考えています。

P.Sは:私は最後の答えであなたを誤解してすみませんグラム++ 4.4.4

+0

'compiler'タグを削除しました。これは一般に、コンパイルプロセス自体についての質問に使用されますが、この質問はC++言語に関するものです。 –

答えて

11

を使用しています、私はそれが簡単になるだろうと一瞬思いました。そこで私はここで完全な解決策を提供しようとします。

形質

:一般的なアプローチは、問題のこのタイプは 特徴ヘルパーテンプレートを作成し、クラスの特殊化を決定する(C++ 11、ブーストまたは手動の実装のいずれか) enable_ifと一緒にそれを使用することで解決します

単純なアプローチ、必ずしもだろう最高の、しかし、書き込みが簡単:

template <typename T> 
struct has_nested_Vec { 
    typedef char yes; 
    typedef char (&no)[2]; 
    template <typename U> 
    static yes test(typename U::Vec* p); 
    template <typename U> 
    static no test(...); 

    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

アプローチは、異なるサイズの型を返すこと、2つのテンプレート機能を提供し、簡単です。そのうちの1つはネストされたVecタイプを取り、もう1つは省略記号を取ります。 Vecがネストされているすべてのタイプでは、最初のオーバーロードがよりよく一致します(省略記号はどのタイプでも最悪一致です)。 Vec SFINAEがネストされていないタイプの場合、そのオーバーロードは破棄され、残りの唯一のオプションは省略記号になります。だから我々はタイプがネストされたVecタイプを持っているかどうか尋ねる特性を持っています。

あなたが任意のライブラリからこれを使用することができ、またはあなた自身をロールバックすることができます

、それは非常に簡単であれば有効にします。

template <bool state, typename T = void> 
struct enable_if {}; 

template <typename T> 
struct enable_if<true,T> { 
    typedef T type; 
}; 

最初の引数がfalseある場合は、ベーステンプレートがあります条件がtrueの場合、enable_ifにはSFINAEで使用できるネストされたtypeがネストされています。typeはネストされていません。

実装

は、今、私たちは、テンプレートとネストされたVecでのみのタイプのSFINAEを使用する特殊化を提供する必要があります:私たちは型にFunctorをインスタンス化するたびに

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
     std::cerr << "general" << std::endl; 
    } 
}; 
template<class T> 
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > { 
    void operator()() const { 
     std::cerr << "special" << std::endl; 
    } 
}; 

、コンパイラは、has_nested_Vecをインスタンス化し、enable_ifに渡された真理値を取得する特殊化を使用しようとします。値がfalseであるタイプの場合、enable_ifにネストされたtypeタイプがないため、SFINAEで特殊化が破棄され、基本テンプレートが使用されます。あなたが本当に全体の形が、ちょうどオペレータを専門とする必要はないと思われるあなたの特定のケースでは、

あなたの特定のケース

、あなたは、単一の一つに三つの要素を混在させることができます。a

template <typename T> 
class Functor { 
    template <typename U> 
    void op_impl(typename U::Vec* p) const { 
     std::cout << "specialized"; 
    } 
    template <typename U> 
    void op_impl(...) const { 
     std::cout << "general"; 
    } 
public: 
    void operator()() const { 
     op_impl<T>(0); 
    } 
}; 
2

これは古い質問ですが、私はそれはまだカップルを提供する価値があると思う:enable_ifの必要性と特性クラスを削除し、Vecの存在に基づいて、2つの内部テンプレートの機能の一つに派遣Functor元のコードをすばやく修正するための選択肢が増えました。

基本的に、問題はSFINAE(実際には問題ない)を使用するのではなく、部分テンプレート(typename T::Vec)で指定された引数とプライマリテンプレートのデフォルトパラメータ(void) 。プライマリテンプレートのデフォルトパラメータのため、Functor<Foo>は実際にはFunctor<Foo, void>を意味します。コンパイラが特殊化を使用してインスタンス化しようとすると、voidstd::vector<int>の代わりに使用できないため、2つの引数を特殊化のものと一致させようとします。その後、プライマリテンプレートを使用してインスタンス化します。のでだから、すべてあなたのVec sがstd::vector<int>のある前提として最速修正は、ラインにこの

template<class T, class E = std::vector<int>> 

template<class T, class V = void> 

を交換する専門は現在、使用されている

引数が一致します。シンプルだが限界がある。明らかに、特殊化の引数のタイプを、プライマリ・テンプレートのデフォルト・パラメータとして指定できるものと一致させるために、より適切に制御する必要があります。新しい特性を定義する必要はありません一つの迅速な解決策はこれです:

#include <iostream> 
#include <vector> 
#include <type_traits> 

template<class T, class E = std::true_type> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

これは基本型と配列、例えば、それらへの参照やポインタを含め、ここでは意味をなさない可能性のあるVecタイプのために動作します。

1

メンバータイプの存在を検出するための別の方法は、void_tを使用することです。有効な部分的な特殊化は、デフォルトのパラメータに一致する限り一般的な実装よりも好ましいので、有効な場合はvoidと評価され、指定されたメンバーが存在する場合にのみ有効な型が欲しい。このタイプは一般にvoid_tとして知られています(そして、C++ 17の時点で、正式に)。

template<class...> 
using void_t = void; 

あなたのコンパイラがそれを正しくサポートしていない場合は(初期のC++ 14のコンパイラでは、エイリアステンプレート内の未使用のパラメータは上記void_tを壊し、SFINAEを確保するために保証されていなかった)、回避策が利用可能です。 C++ 17、void_tのよう

template<typename... Ts> struct make_void { typedef void type; }; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 
type_traits

に、ユーティリティライブラリで利用可能です。これにより

#include <iostream> 
#include <vector> 
#include <type_traits> // For void_t. 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

// Use void_t here. 
template<class T> 
struct Functor<T, std::void_t<typename T::Vec>> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

意図したように、出力は、specialです。


この場合、メンバータイプの存在を確認しているので、プロセスは非常に簡単です。式SFINAEまたはtype_traitsライブラリなしで行うことができ、必要に応じてC++ 03の機能を使用するようにチェックを書き直すことができます。私の知る限り

// void_t: 
// Place above Functor's definition. 
template<typename T> struct void_t { typedef void type; }; 

// ... 

template<class T> 
struct Functor<T, typename void_t<typename T::Vec>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

、これがほとんどで動作するはずです、全部ではないが、SFINAE対応のC++ 03-、C++、11-、C++、14-、またはC++ 1Z準拠のコンパイラ。これは、標準より少し遅れているコンパイラを扱う場合や、C++ 11互換のコンパイラがまだないプラットフォーム用にコンパイルする場合に便利です。


void_tの詳細については、cppreferenceを参照してください。

関連する問題

 関連する問題