クラスに指定されたシグネチャのメンバー関数があるかどうかを確認する
質問
クラスに特定のシグネチャの特定のメンバー関数があるかどうかを検出するテンプレート トリックを求めています。
この問題はここで引用したものと似ていますhttp://www.gotw.ca/gotw/071.htmしかし同じではありません:サッター氏の本の項目では、クラス C は特定のシグネチャを持つメンバー関数を提供しなければならない、そうしないとプログラムはコンパイルできないという質問に答えています。私の問題では、クラスにその関数がある場合は何かをし、そうでない場合は「別の何か」を行う必要があります。
boost::serialization も同様の問題に直面しましたが、私は彼らが採用した解決策が好きではありません。特定のシグネチャを持つ特定のメンバー関数 (この場合、特定の型の 2 つのパラメータを取る「シリアル化」) を定義しない限り、デフォルトで特定のシグネチャを持つ自由な関数 (定義する必要がある) を呼び出すテンプレート関数。それ以外の場合は、コンパイルエラーが発生します。つまり、侵入型シリアル化と非侵入型シリアル化の両方を実装することになります。
私は次の 2 つの理由からその解決策が好きではありません。
- 非侵入的にするには、boost::serialization 名前空間にあるグローバル「serialize」関数をオーバーライドする必要があります。そのため、名前空間 boost と名前空間シリアル化を開くためのクライアント コードが必要です。
- その混乱を解決するスタックは、10〜12の機能の呼び出しでした。
そのメンバー関数を持たないクラスのカスタム動作を定義する必要があり、エンティティが異なる名前空間内にあります (また、ある名前空間で定義されているグローバル関数を、別の名前空間にいるときにオーバーライドしたくありません)。
このパズルを解くためのヒントを教えていただけますか?
解決
私の理解が正しいかどうかはわかりませんが、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 の機能に依存した実装例を次に示します。関数が継承されている場合でも、関数を正しく検出します(Mike Kinghanが次のように観察しているように、受け入れられた回答の解決策とは異なります) 彼の答え).
このスニペットがテストする関数は次のとおりです 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
Compiletime Member-Function Intropectionのこの質問に対する受け入れられた答えは、それは正当に人気がありますが、次のプログラムで観察できる障害があります。
#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>
する ない 提供する 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*()
それ自体でテンプレートのインスタンス化は「継承を行う」ものではありません。
この障害は、「sizeof()トリック」を使用して、よく知られているsfinaeアプローチに影響を与えません。 T
何らかのメンバー関数を持っています mf
(例を参照)この答え とコメント)。しかし、それを確立します T::mf
存在するだけでは (通常は?) 十分ではないことがよくあります。また、目的の署名があることを確立する必要がある場合があります。それが、図解されたテクニックのスコアです。目的の署名のポイティ化されたバリアントは、テンプレートタイプのパラメーターに刻まれています。&T::mf
SFINAE プローブが成功するために。しかし、このテンプレートのインスタンス化手法は、間違った答えを与えます T::mf
継承されます。
コンパイル時のイントロスペクションのための安全な SFINAE 手法 T::mf
の使用を避ける必要があります &T::mf
sfinae関数テンプレート解像度が依存するタイプをインスタンス化するテンプレート引数内。代わりに、SFINAEテンプレート関数解像度は、過負荷型SFINAEプローブ関数の引数タイプとして使用される正確に適切なタイプ宣言にのみ依存できます。
この制約を守る質問への答えによって、私は E T::operator*() const
, 、任意のため T
そして E
. 。同じパターンが当てはまります 準用他のメンバーのメソッド シグネチャを調査します。
#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
?
1回の呼び出しの返品タイプを評価することにより、回答を取得します test(0,0)
. 。それは次のように行われます。
typedef decltype(test<T>(0,0)) type;
この呼び出しは次のように解決される可能性があります。 /* SFINAE operator-exists :) */
過負荷 test()
, 、または、次のように解決される可能性があります。 /* SFINAE game over :( */
過負荷。それは解決できません /* SFINAE operator-has-correct-sig :) */
過負荷、それは1つの議論だけを期待しており、2つを渡しているからです。
なぜ二人を追い越してしまったのでしょうか?単に決議を強制的に除外するため/* SFINAE operator-has-correct-sig :) */
. 。2 番目の引数にはそれ以外の意味はありません。
この電話は test(0,0)
に解決します /* SFINAE operator-exists :) */
最初の引数0がその過負荷の最初のパラメータータイプを満たした場合に備えて、それは decltype(&A::operator*)
, 、 と A = T
. 。念のため、0はそのタイプを満たします T::operator*
存在します。
コンパイラーがこれに対して「はい」と言ったとします。それから、それは進みます/* 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
.
次の場合、コンパイラはここで「Yes」と表示します。 T::operator*
その目的の署名があり、それから再び過負荷のリターンタイプを評価する必要があります。もう「再帰」はありません:それはです std::true_type
.
コンパイラが選択しない場合 /* SFINAE operator-exists :) */
呼び出しのために test(0,0)
あるいは選ばない /* SFINAE operator-has-correct-sig :) */
電話のために test(&A::operator*)
, 、どちらの場合も次のようになります/* 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;
メンバーの列挙型を確認する 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;
または
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;
};
マクロ (エル ディアブロ!):
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 struct (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);
}
ここでは、Mike Kinghan の答えを簡単に説明します。これにより、継承されたメソッドが検出されます。また、 ちょうど (引数の変換を許可する 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;
私自身も同じ種類の問題を抱えていましたが、ここで提案されている解決策は非常に興味深いものでした...しかし、次のようなソリューションの要件がありました。
- 継承された関数も検出します。
- C++11 対応でないコンパイラと互換性があります (したがって decltype はありません)
別のを見つけました 糸 に基づいてこのようなことを提案する ブーストディスカッション。以下は、次のモデルに従って、特性クラスの 2 つのマクロ宣言として提案されたソリューションを一般化したものです。 boost::has_* クラス。
#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)
}
これを実現するには、以下を使用する必要があります。
- 関数テンプレートのオーバーロード メソッドが利用可能かどうかに応じて戻り値の型が異なります
- のメタ条件に従って、
type_traits
ヘッダーを返します。true_type
またはfalse_type
私たちの過負荷から - を宣言します
true_type
過負荷を期待してint
そしてそのfalse_type
可変引数パラメーターの利用を期待するオーバーロード: 「オーバーロード解決における省略記号変換の優先順位が最も低い」 - のテンプレート仕様を定義する際に、
true_type
使用する関数declval
そしてdecltype
メソッド間の戻り値の型の違いやオーバーロードに関係なく関数を検出できるようになります。
この実際の例を見ることができます ここ. ただし、以下でも説明します。
という関数の存在を確認したい 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
インラインコードまたはメタプログラミングの目的。
邪魔にならないように、次のようにすることもできます serialize
シリアル化されているクラスまたはアーカイブ クラスの名前空間内で、おかげで ケーニッヒ検索. 。見る 無料の関数オーバーライド用の名前空間 詳細については。:-)
無料の関数を実装するために任意の名前空間を開くことは、単純に間違っています。(例: 名前空間を開いてはいけない std
実装する swap
独自のタイプについては使用できますが、代わりに Koenig ルックアップを使用する必要があります。)
わかった。2回目の試み。これが気に入らなくても大丈夫です。もっとアイデアを探しています。
Herb Sutter の記事では特性について説明しています。したがって、デフォルトのインスタンス化にフォールバック動作を持つ特性クラスを作成でき、メンバー関数が存在するクラスごとに、特性クラスがメンバー関数の呼び出しに特化されます。Herb の記事には、多くのコピー アンド ペーストを行わずにこれを行うためのテクニックが記載されていると思います。
ただし、先ほども述べたように、そのメンバーを実装するクラスの「タグ付け」に伴う余分な作業は望まないかもしれません。その場合、私は 3 番目の解決策を検討しています...
C++11 サポートなし (decltype
)これはうまくいくかもしれません:
SSCCE
#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
function は、一般的な対応物がないまま残されている、別の SFINAE 種類のテンプレートです。したがって、引数の型が特殊化された型と一致する場合にのみインスタンス化できます。
どちらも test
関数と enum 宣言が最終的に中心となる SFINAE パターンを形成します。省略記号を使用して、 false_type
そして、より具体的な議論を持つ相手が優先されます。
インスタンス化できるようにするには、 test
テンプレート引数を持つ関数 T
, 、 match
関数の戻り値の型はインスタンス化する必要があるため、関数をインスタンス化する必要があります。 TypeSink
口論。注意点は、 &U::foo
, は、関数の引数でラップされています。 ない テンプレート引数の特殊化内から参照されるため、継承されたメンバーの検索は引き続き行われます。
あなたが探している答えがここにあると信じています。
http://www.martinecker.com/wiki/index.php?title=コンパイル時のオペレーターの存在の検出
そして、もう少し充実した例がここにあります
サポートの存在を検出する技術を使用します。 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;
}
実装の詳細は前の回答と同じですが、ライブラリを使用する方が簡単です。