より多くのテンプレートメタプログラミングを始めたいです。 SFINAEは「置換の失敗はエラーではない」という意味です。しかし、誰かが私にSFINAEの良い使い方を見せてもらえますか?
例を1つ示します( from from ):
template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
// Will be chosen if T is anything except a class.
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};
IsClassT<int>::Yes
が評価される場合、intはクラスではないため、0をint int::*
に変換できません。したがって、メンバーポインターを持つことはできません。 SFINAEが存在しなかった場合、「0は非クラス型intのメンバーポインターに変換できません」などのコンパイラエラーが発生します。代わりに、Twoを返す...
フォームを使用するだけで、falseと評価されます。intはクラス型ではありません。
SFINAE
を使用してブール条件をチェックするのが好きです。
_template<int I> void div(char(*)[I % 2 == 0] = 0) {
/* this is taken when I is even */
}
template<int I> void div(char(*)[I % 2 == 1] = 0) {
/* this is taken when I is odd */
}
_
とても便利です。たとえば、演算子コンマを使用して収集された初期化リストが固定サイズ以下であるかどうかを確認するために使用しました
_template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}
_
リストは、MがNよりも小さい場合にのみ受け入れられます。つまり、初期化リストに含まれる要素が多すぎません。
構文char(*)[C]
の意味:要素タイプcharおよびサイズC
の配列へのポインター。 C
がfalse(ここでは0)の場合、無効なタイプchar(*)[0]
、ゼロサイズの配列へのポインターを取得します。SFINAEは、テンプレートが無視されるようにします。
_boost::enable_if
_で表現され、次のようになります
_template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i,
typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}
_
実際には、条件をチェックする機能は便利な機能であることがよくあります。
C++ 11では、SFINAEテストははるかにきれいになりました。一般的な使用法の例を次に示します。
特性に応じて関数のオーバーロードを選択します
template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
//integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
//floating point version
}
いわゆるタイプシンクイディオムを使用すると、メンバーがあるかどうか、そのメンバーが特定のタイプであるかどうかを確認するなど、タイプに対してかなり任意のテストを実行できます。
//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;
//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};
struct S{
int bar;
};
struct K{
};
template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
std::cout << "has bar" << std::endl;
}
void print(...){
std::cout << "no bar" << std::endl;
}
int main(){
print(S{});
print(K{});
std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}
以下にライブの例を示します。 http://ideone.com/dHhyHE 私は最近、ブログでSFINAEとタグのディスパッチに関するセクション全体を書きました(恥知らずなプラグインですが、関連性があります) http:/ /metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html
C++ 14の時点で、std :: void_tがあることに注意してください。これは、私のTypeSinkと本質的に同じです。
Boostの enable_if ライブラリは、SFINAEを使用するためのきれいなインターフェイスを提供します。私のお気に入りの使用例の1つは、 Boost.Iterator ライブラリです。 SFINAEは、イテレータタイプの変換を有効にするために使用されます。
C++ 17は、おそらく機能を照会する一般的な手段を提供します。詳細については N4502 を参照してください。ただし、自己完結型の例として、以下を考慮してください。
この部分は定数部分であり、ヘッダーに入れます。
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
N4502 から取得した次の例は、使用法を示しています。
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
他の実装と比較して、これは非常にシンプルです。ツールのセットが削減されます(void_t
およびdetect
)で十分です。また、以前のアプローチよりもかなり効率的であることが報告されました( N4502 を参照)。
以下に 実例 を示します。これには、GCC 5.1以前の移植性の調整が含まれています。
ここに別の(後期) [〜#〜] sfinae [〜#〜] の例があります。これは Greg Rogers の answer に基づいています。
template<typename T>
class IsClassT {
template<typename C> static bool test(int C::*) {return true;}
template<typename C> static bool test(...) {return false;}
public:
static bool value;
};
template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);
このようにして、value
の値を確認して、T
がクラスであるかどうかを確認できます。
int main(void) {
std::cout << IsClassT<std::string>::value << std::endl; // true
std::cout << IsClassT<int>::value << std::endl; // false
return 0;
}
SFINAEの良い記事の1つです。 C++のSFINAEコンセプトの紹介:クラスメンバーのコンパイル時のイントロスペクション 。
次のように要約します。
/*
The compiler will try this overload since it's less generic than the variadic.
T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
It simply tries the next overload.
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }
// The sink-hole.
void f(...) { }
f(1); // Calls void f(...) { }
template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.
template<class T> // A specialisation used if the expression is true.
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.
template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return obj.serialize();
}
template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return to_string(obj);
}
declval
は、簡単に構築できないタイプのオブジェクトへの「偽の参照」を提供するユーティリティです。 declval
は、SFINAEの構築に本当に便利です。
struct Default {
int foo() const {return 1;}
};
struct NonDefault {
NonDefault(const NonDefault&) {}
int foo() const {return 1;}
};
int main()
{
decltype(Default().foo()) n1 = 1; // int n1
// decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
std::cout << "n2 = " << n2 << '\n';
}