クラスに特定の署名の特定のメンバー関数があるかどうかを検出するためのテンプレートトリックを求めています。
問題はここで引用したものと似ています http://www.gotw.ca/gotw/071.htm しかし同じではありません:サッターの本の項目で、彼はクラスがCは特定の署名を持つメンバー関数を提供する必要があります。そうしないと、プログラムはコンパイルされません。私の問題では、クラスにその機能がある場合は何かをする必要があり、そうでない場合は「何か他のこと」を行います。
Boost :: serializationが同様の問題に直面しましたが、彼らが採用したソリューションは好きではありません:特定のメンバー関数を定義しない限り、特定のシグネチャでデフォルトでフリー関数(定義する必要がある)を呼び出すテンプレート関数(その場合、特定の署名を持つ特定のタイプの2つのパラメーターを受け取る「シリアル化」を行うと、コンパイルエラーが発生します。これは、侵入型および非侵入型の両方のシリアル化を実装することです。
私は2つの理由でそのソリューションが好きではありません:
そのメンバー関数を持たないクラスのカスタム動作を定義する必要があり、エンティティは異なるネームスペース内にあります(そして、あるネームスペースで定義されているグローバル関数を別のネームスペースにある間にオーバーライドしたくありません)
このパズルを解決するためのヒントを教えてください。
あなたを正しく理解しているかどうかはわかりませんが、SFINAEを利用して、コンパイル時に関数の存在を検出できます。私のコードの例(クラスにメンバー関数size_t used_memory()constがあるかどうかをテストします)。
template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
C++ 11の機能に依存する可能な実装を次に示します。継承されていても、関数を正しく検出します(マイク・キングハンが his answer で観察しているように、受け入れられた答えの解決策とは異なります)。
このスニペットがテストする関数はserialize
と呼ばれます:
#include <type_traits>
// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.
template<typename, typename T>
struct has_serialize {
static_assert(
std::integral_constant<T, false>::value,
"Second template parameter needs to be of function type.");
};
// specialization that does the checking
template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
template<typename T>
static constexpr auto check(T*)
-> typename
std::is_same<
decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>::type; // attempt to call it and see if the return type is correct
template<typename>
static constexpr std::false_type check(...);
typedef decltype(check<C>(0)) type;
public:
static constexpr bool value = type::value;
};
使用法:
struct X {
int serialize(const std::string&) { return 42; }
};
struct Y : X {};
std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
コンパイル時のメンバー関数のイントロスペクションのこの質問に対する受け入れられた答えは、それはまさしく人気がありますが、以下のプログラムで観察できるスナッグを持っています:
_#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
_
GCC 4.6.3で構築されたプログラムは_110
_を出力します-_T = std::shared_ptr<int>
_がnotを提供することを通知しますint & T::operator*() const
。
この落とし穴にまだ賢明ではない場合は、ヘッダー_std::shared_ptr<T>
_の_<memory>
_の定義を確認してください。その実装では、_std::shared_ptr<T>
_は、operator*() const
を継承する基本クラスから派生します。したがって、_SFINAE<U, &U::operator*>
_の演算子を「見つける」ことを構成するテンプレートのインスタンス化_U = std::shared_ptr<T>
_は発生しません。これは、_std::shared_ptr<T>
_自体にoperator*()
がなく、テンプレートのインスタンス化が継承"。
このスナッグは、T
が何らかのメンバー関数mf
を持っているかどうかだけを検出するために、 "sizeof()トリック"を使用する既知のSFINAEアプローチには影響しません(たとえば この回答を参照 およびコメント)。ただし、_T::mf
_が存在することを確認するだけでは十分ではないことがよくあります。目的の署名があることを確認する必要があります。それが、図示された技術が採点する場所です。目的の署名のポインター化されたバリアントは、SFINAEプローブが成功するために_&T::mf
_によって満たされる必要があるテンプレート型のパラメーターに刻まれています。ただし、_T::mf
_が継承されると、このテンプレートのインスタンス化手法は間違った答えを返します。
_T::mf
_のコンパイル時イントロスペクションのための安全なSFINAEテクニックは、SFINAE関数テンプレート解決が依存する型をインスタンス化するために、テンプレート引数内で_&T::mf
_の使用を避ける必要があります。代わりに、SFINAEテンプレート関数の解決は、オーバーロードされたSFINAEプローブ関数の引数タイプとして使用される正確に適切なタイプ宣言のみに依存できます。
この制約に従う質問への回答として、任意のT
およびE
のE T::operator*() const
のコンパイル時検出について説明します。同じパターンが必要な変更を適用して、他のメンバーメソッドシグネチャをプローブします。
_#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
_
このソリューションでは、オーバーロードされたSFINAEプローブ関数test()
は「再帰的に呼び出されます」。 (もちろん、実際にはまったく呼び出されません;コンパイラによって解決される仮想的な呼び出しの戻り値の型を持っているだけです。)
少なくとも1つ、多くても2つの情報ポイントをプローブする必要があります。
T::operator*()
はまったく存在しますか?そうでない場合は、完了です。T::operator*()
が存在する場合、その署名はE T::operator*() const
ですか?test(0,0)
への1回の呼び出しの戻り値の型を評価することで、答えを取得します。それは以下によって行われます:
_ typedef decltype(test<T>(0,0)) type;
_
この呼び出しは、test()
の_/* SFINAE operator-exists :) */
_オーバーロードに解決されるか、_/* SFINAE game over :( */
_オーバーロードに解決される場合があります。 _/* SFINAE operator-has-correct-sig :) */
_オーバーロードに解決することはできません。1つの引数のみを期待し、2つの引数を渡すためです。
なぜ2つ合格しているのですか? _/* SFINAE operator-has-correct-sig :) */
_を除外するように解決を強制するだけです。 2番目の引数には他の意味はありません。
test(0,0)
のこの呼び出しは、最初の引数0がそのオーバーロードの最初のパラメータータイプ(decltype(&A::operator*)
)を_/* SFINAE operator-exists :) */
_で満たす場合にのみ_A = T
_に解決されます。 _T::operator*
_が存在する場合、0はその型を満たします。
コンパイラがそれに対して「はい」と言ったとしましょう。次に、_/* SFINAE operator-exists :) */
_を使用し、関数呼び出しの戻り値の型を決定する必要があります。その場合、decltype(test(&A::operator*))
-test()
のさらに別の呼び出しの戻り値の型です。
今回は、1つの引数_&A::operator*
_のみを渡します。これは、現在存在することがわかっているか、ここにないことを意味します。 test(&A::operator*)
の呼び出しは、_/* SFINAE operator-has-correct-sig :) */
_に解決される場合もあれば、再度_/* SFINAE game over :( */
_に解決される場合もあります。呼び出しは、_/* SFINAE operator-has-correct-sig :) */
_が_&A::operator*
_でそのオーバーロードの単一のパラメーター型(E (A::*)() const
)を満たす場合にのみ_A = T
_と一致します。
コンパイラは、_T::operator*
_に必要な署名がある場合、ここで「はい」と言ってから、再びオーバーロードの戻り値の型を評価する必要があります。これ以上の「再帰」はありません。それは_std::true_type
_です。
コンパイラが呼び出しtest(0,0)
に_/* SFINAE operator-exists :) */
_を選択しない場合、または呼び出しtest(&A::operator*)
に_/* SFINAE operator-has-correct-sig :) */
_を選択しない場合、いずれの場合も_/* SFINAE game over :( */
_になります。最終的な戻り値の型は_std::false_type
_です。
さまざまなケースのサンプルで予想される回答を生成するテンプレートを示すテストプログラムを次に示します(GCC 4.6.3を再度参照)。
_// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
_
このアイデアに新しい欠陥はありますか?それが回避する障害のファウルをもう一度落とすことなく、より一般的にすることができますか?
使用方法のスニペットを次に示します。*これらすべての根性はさらに下にあります
指定されたクラスのメンバーx
を確認します。var、func、class、union、enumのいずれかです
_CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
_
メンバー関数の確認void x()
:
_//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
_
メンバー変数x
:を確認
_CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
_
メンバークラスx
:を確認
_CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
_
メンバーユニオンx
:を確認
_CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
_
メンバーenum x
:を確認
_CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
_
署名に関係なくメンバー関数x
を確認します:
_CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
_
OR
_CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
_
詳細とコア:
_/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
_
マクロス(El Diablo!):
CREATE_MEMBER_CHECK:
_//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
_
CREATE_MEMBER_VAR_CHECK:
_//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
_
CREATE_MEMBER_FUNC_SIG_CHECK:
_//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
_
CREATE_MEMBER_CLASS_CHECK:
_//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
_
CREATE_MEMBER_UNION_CHECK:
_//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
_
CREATE_MEMBER_ENUM_CHECK:
_//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
_
CREATE_MEMBER_FUNC_CHECK:
_//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
_
CREATE_MEMBER_CHECKS:
_//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
_
期待しているメンバー関数の名前がわかっている場合、これで十分です。 (この場合、メンバー関数がない場合、関数blaはインスタンス化に失敗します(関数の部分的な特殊化がないため、とにかく動作する関数を作成するのは困難です。クラステンプレートを使用する必要がある場合があります) enable_ifに似ています)は、メンバーとして使用する関数のタイプにテンプレート化することもできます。
template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
A a;
B b;
bla(b);
bla(a);
}
これを実現するには、次を使用する必要があります。
type_traits
_ ヘッダーのメタ条件に合わせて、 _true_type
_または_false_type
_ オーバーロードからint
を期待する_true_type
_オーバーロードと、Variadicパラメーターを活用することを期待する_false_type
_オーバーロードを宣言します: "オーバーロード解決におけるEllipsis変換の最低優先順位"true_type
_関数のテンプレート仕様を定義する際に、 declval
および decltype
を使用して、メソッド間の戻り値の型の違いやオーバーロードとは無関係に関数を検出するこれの実例を見ることができます here 。しかし、私もそれを以下に説明します:
test
から変換可能な型をとるint
という名前の関数の存在を確認したい場合、次の2つの関数を宣言する必要があります。
_template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
_
decltype(hasTest<a>(0))::value
はtrue
です(void a::test()
オーバーロードに対処するための特別な機能を作成する必要はありません。void a::test(int)
は受け入れられます)decltype(hasTest<b>(0))::value
はtrue
です(int
はdouble
に変換可能であるため、戻り型に関係なくint b::test(double)
が受け入れられます)decltype(hasTest<c>(0))::value
はfalse
です(c
にはtest
から変換可能な型を受け入れるint
という名前のメソッドがないため、これは受け入れられません)このソリューションには2つの欠点があります。
test()
メソッドをテストする関数の名前は何でしょうか?そのため、これらの関数は詳細名前空間で宣言することが重要です。クラスでのみ使用する場合は、そのクラスでプライベートに宣言するのが理想的です。そのために、この情報を抽象化するのに役立つマクロを作成しました。
_#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
_
次のように使用できます:
_namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}
_
続いて_details::test_int<a>::value
_または_details::test_void<a>::value
_を呼び出すと、インラインコードまたはメタプログラミングの目的でtrue
またはfalse
が生成されます。
マイク・キングハンの答えをより簡単に説明します。これにより、継承されたメソッドが検出されます。また、exact署名もチェックします(引数変換を許可するjrokのアプローチとは異なります)。
template <class C>
class HasGreetMethod
{
template <class T>
static std::true_type testSignature(void (T::*)(const char*) const);
template <class T>
static decltype(testSignature(&T::greet)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");
実行可能 例
使用できますstd :: is_member_function_pointer
class A {
public:
void foo() {};
}
bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
自分で同じ種類の問題に遭遇し、ここで提案された解決策が非常に興味深いことがわかりました...しかし、次のような解決策が必要でした:
BOOSTディスカッション に基づいて、別の thread を提案します。 boost :: has _ * クラスのモデルに従って、特性クラスの2つのマクロ宣言として提案されたソリューションを一般化しています。
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>
/// Has constant function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)
/// Has non-const function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)
// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...) \
template \
< typename Type, \
bool is_class = boost::is_class<Type>::value \
> \
class has_func_ ## func_name; \
template<typename Type> \
class has_func_ ## func_name<Type,false> \
{public: \
BOOST_STATIC_CONSTANT( bool, value = false ); \
typedef boost::false_type type; \
}; \
template<typename Type> \
class has_func_ ## func_name<Type,true> \
{ struct yes { char _foo; }; \
struct no { yes _foo[2]; }; \
struct Fallback \
{ func_ret_type func_name( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const) {} \
}; \
struct Derived : public Type, public Fallback {}; \
template <typename T, T t> class Helper{}; \
template <typename U> \
static no deduce(U*, Helper \
< func_ret_type (Fallback::*)( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const), \
&U::func_name \
>* = 0 \
); \
static yes deduce(...); \
public: \
BOOST_STATIC_CONSTANT( \
bool, \
value = sizeof(yes) \
== sizeof( deduce( static_cast<Derived*>(0) ) ) \
); \
typedef ::boost::integral_constant<bool,value> type; \
BOOST_STATIC_CONSTANT(bool, is_const = func_const); \
typedef func_ret_type return_type; \
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; \
}
// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__
これらのマクロは、次のプロトタイプを使用して特性クラスに展開されます。
template<class T>
class has_func_[func_name]
{
public:
/// Function definition result value
/** Tells if the tested function is defined for type T or not.
*/
static const bool value = true | false;
/// Function definition result type
/** Type representing the value attribute usable in
http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
*/
typedef boost::integral_constant<bool,value> type;
/// Tested function constness indicator
/** Indicates if the tested function is const or not.
This value is not deduced, it is forced depending
on the user call to one of the traits generators.
*/
static const bool is_const = true | false;
/// Tested function return type
/** Indicates the return type of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef func_ret_type return_type;
/// Tested function arguments types
/** Indicates the arguments types of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};
それで、これからできる典型的な使い方は何ですか?
// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
// Next line will declare the traits class
// to detect the member function void foo(int,int) const
DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}
// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>
// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ _this_.foo(a,b);
}
// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ default_foo(_this_,a,b);
}
// Let us declare test types
struct empty
{
};
struct direct_foo
{
void foo(int,int);
};
struct direct_const_foo
{
void foo(int,int) const;
};
struct inherited_const_foo :
public direct_const_foo
{
};
// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
int a;
foo_bar(a); // calls default_foo
empty b;
foo_bar(b); // calls default_foo
direct_foo c;
foo_bar(c); // calls default_foo (member function is not const)
direct_const_foo d;
foo_bar(d); // calls d.foo (member function is const)
inherited_const_foo e;
foo_bar(e); // calls e.foo (inherited member function)
}
邪魔にならないようにするには、 Koenigルックアップ のおかげで、シリアル化されるクラスまたはアーカイブクラスの名前空間にserialize
を配置することもできます。詳細については、 フリー関数オーバーライドのネームスペース を参照してください。 :-)
任意の名前空間を開いて無料の関数を実装するのは、単に間違っています。 (たとえば、名前空間std
を開いて独自の型にswap
を実装することは想定されていませんが、代わりにKoenigルックアップを使用する必要があります。)
はい。もう一度試してください。これも気に入らなくても大丈夫です。もっとアイデアを探しています。
ハーブサッターの記事では、形質について説明しています。そのため、デフォルトのインスタンス化がフォールバック動作を持つ特性クラスを持つことができ、メンバー関数が存在する各クラスに対して、特性クラスはメンバー関数を呼び出すために特化されます。ハーブの記事では、これを行うためのテクニックについて言及しているので、コピーと貼り付けをあまり必要としないと思います。
しかし、私が言ったように、おそらく、そのメンバーを実装する「タグ付け」クラスに関連する余分な作業を望まないでしょう。その場合、私は3番目の解決策を見ています...
C++ 11サポート(decltype
)がなければ、これは機能する可能性があります。
#include <iostream>
using namespace std;
struct A { void foo(void); };
struct Aa: public A { };
struct B { };
struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };
template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];
template<int>
struct TypeSink;
template<class U>
static true_type &match(U);
template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);
template<class U>
static false_type &test(...);
enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};
int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;
cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}
A
、Aa
、およびB
は問題のクラスであり、Aa
は探しているメンバーを継承する特別なクラスです。
FooFinder
のtrue_type
およびfalse_type
は、対応するC++ 11クラスの代替です。また、テンプレートメタプログラミングを理解するために、SFINAE-sizeof-trickの基礎を明らかにします。
TypeSink
は、後でsizeof
演算子の積分結果をテンプレートのインスタンス化にシンクして型を形成するために使用されるテンプレート構造体です。
match
関数は、SFINAEの別の種類のテンプレートであり、汎用の対応物はありません。したがって、引数の型が特化された型と一致する場合にのみインスタンス化できます。
test
関数と列挙宣言の両方が、最終的に中心的なSFINAEパターンを形成します。 false_type
を返すEllipsisを使用する一般的なものと、より具体的な引数を持つ同等物が優先されます。
test
のテンプレート引数を使用してT
関数をインスタンス化できるようにするには、match
引数をインスタンス化するために戻り型が必要なので、TypeSink
関数をインスタンス化する必要があります。警告は、関数引数にラップされている&U::foo
は、テンプレート引数の特殊化内からnotで参照されるため、継承されたメンバー検索が引き続き行われることです。
あなたが探している答えはここにあると信じています。
http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time
ここにもう少し記入した例
この手法を使用して、問題のクラスでサポートするostream演算子<<の存在を検出してから、異なるビットのコードを生成します。
リンクされたソリューションを見つける前にそれが可能であるとは思いませんでしたが、それは非常に巧妙なトリックです。コードを理解する時間を費やし、それは非常に価値があります。
ブラッド
Facebook follyを使用している場合は、すぐに使えるマクロが用意されています:
#include <folly/Traits.h>
namespace {
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace
void some_func() {
cout << "Does class Foo have a member int test() const? "
<< boolalpha << has_test_traits<Foo, int() const>::value;
}
実装の詳細は前の回答と同じですが、ライブラリを使用する方が簡単です。