クラスに特定のシグネチャのメンバー関数があるかどうかを確認する


135

私は、クラスが特定の署名の特定のメンバー関数を持っているかどうかを検出するためのテンプレートトリックを求めています。

問題はここで引用したものと似てい ます が、同じではありません。Sutterの本の項目で、クラスCがメンバー関数を提供する必要があるという質問に答えました特定の署名がないと、プログラムはコンパイルされません。私の問題では、クラスにその機能がある場合は何かをする必要があります。

同様の問題がboost :: serializationにも直面しましたが、私が彼らが採用した解決策は好きではありません。特定のメンバー関数を定義しない限り、デフォルトで特定のシグネチャを持つフリー関数(定義する必要がある)を呼び出すテンプレート関数(彼らの場合、特定のシグネチャを持つ特定のタイプの2つのパラメータをとる「シリアライズ」、それ以外の場合、コンパイルエラーが発生します。それは、侵入型と非侵入型の両方のシリアライゼーションを実装することです。

次の2つの理由で、このソリューションは好きではありません。

  1. 非侵入型にするには、boost :: serialization名前空間にあるグローバルな「serialize」関数をオーバーライドする必要があります。これにより、クライアントコードで名前空間のブーストと名前空間のシリアル化を開くことができます。
  2. この混乱を解決するためのスタックは、10〜12の関数呼び出しでした。

そのメンバー関数を持たないクラスのカスタム動作を定義する必要があり、エンティティは異なる名前空間内にあります(ある名前空間で定義されているグローバル関数を別の名前空間にある間にオーバーライドしたくない)

このパズルを解くためのヒントを教えてもらえますか?



@ R.MartinhoFernandesどのような答えを探していますか?Mike Kinghanによるこの回答は、かなり深く、C ++ 11のものを使用しています。
jrok 2013年

@ R.MartinhoFernandesたぶんこれはあなたが探している最新バージョンですか?
Daniel Frey

回答:


90

私があなたを正しく理解しているかどうかはわかりませんが、コンパイル時に関数の存在を検出するために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>());
}

14
これはいったい何なのですか???それは合法的なC ++コードですか?「template <typename U、size_t(U :: *)()const>」と書けますか?しかし...それは素晴らしい新しいソリューションです!ありがとう、明日は同僚とよく分析します。
ugasoft 2008

2
この例には、「int_to_type」の定義がありません。明らかにそれは答えに追加されませんが、それは人々が簡単なカットアンドペーストの後に実際のコードを見ることができることを意味します。
Richard Corden

2
int_to_typeの簡単な定義は次のようになります: 'template <int N> struct int_to_type {};'。多くの実装では、パラメータN値を列挙型または静的整数定数に保持しています(template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
デビッドロドリゲス-ドリベス

2
int_to_typeの代わりにboost :: integral_constantを使用するだけです。
Vadim Ferderer

2
@JohanLundbergこれは、(非静的)メンバー関数へのポインターです。たとえば、size_t(std::vector::*p)() = &std::vector::size;
2015

133

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

Yに「シリアライズ」と呼ばれるメソッドがない場合、これは機能しますか?「serialize」メソッドが存在しなかった場合にfalse値を返す方法はわかりません。
コリン

1
その場合、@ Collinはチェックの最初のオーバーロードでテンプレートパラメータの置換が失敗し、オーバーロードセットから破棄されます。これは、false_typeを返す2番目のものに戻ります。SFINAEの原則により、これはコンパイラエラーではありません。
jrok 2014

1
@ elios264ありません。マクロを使用して、チェックする各関数のテンプレートを作成できます。
jrok

1
チェックの引数がTまたはT&ではなくT *型である特別な理由は何ですか?
Shibumi

1
しかし、serializeそれ自体がテンプレートを受け入れる場合はどうでしょうか。serialize正確なタイプを入力せずに存在をテストする方法はありますか?
Hi-Angel

37

コンパイル時のメンバー関数のイントロスペクションに関するこの質問への受け入れられた回答は、人気がありますが、次のプログラムで観察できる問題があります。

#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()Trick"を使用する、よく知られている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つを渡すため、オーバーロードを解決できません。

なぜ2つ合格するのですか?単に解像度を強制的に除外し /* SFINAE operator-has-correct-sig :) */ます。2番目の引数には他の意味はありません。

このコールtest(0,0)への意志の解決/* SFINAE operator-exists :) */だけである過負荷の最初の引数0 satifies最初のパラメータタイプ、ケース内decltype(&A::operator*)に、A = TT::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::*)() constA = T

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;
}

このアイデアに新しい欠陥はありますか?それが回避する思わぬ障害に再び反することなく、より一般的にすることはできますか?


16

ここにいくつかの使用法の抜粋があります:*これらすべての根性はさらに下にあります

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;
};

マクロ(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)

1
これは素晴らしい; これを単一のヘッダーファイルライブラリに配置すると便利です。
アラン

12

予期しているメンバー関数の名前がわかっている場合は、これで十分です。(この場合、メンバー関数がない場合、関数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);
}

4
やった!これは、yrpによって提案されたソリューションに似ています。テンプレートがメンバー関数を介してテンプレート化できることを知りませんでした。それは私が今日学んだ新しい機能です!...そして新しいレッスン:「C ++の専門家だと絶対に言わないでください」:)
ugasoft 2008

7

これがマイク・キングハンの答えの簡単な見方です。これにより、継承されたメソッドが検出されます。また、正確な署名をチェックします(引数の変換を許可する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, "");

実行可能な


これは良いことですが、関数が引数を取らない場合は機能しません
Triskeldeian

それは素晴らしい働きをします。引数を取らないメンバー関数にこのトリックを適用しても問題はありませんでした。
JohnB '27 / 11/16

これは、オーバーロードを含め、継承を含め、メソッドusingクラス引数がなく、基本クラスからオーバーロードを取得するために使用する場合、私にとってはうまく機能します。MSVC 2015とClang-CLで動作します。ただし、MSVC 2012では機能しません。
steveire 2017

5

std :: is_member_function_pointerを使用できます

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

16
ありません&A::fooありませんがあるかどうコンパイルエラーことfooですべてではA?私は元の質問を、何らかの名前のメンバーを持つ名前のクラスだけでなく、任意の入力クラスで機能することになっていると読みましたfoo
ジェフウォルデン

5

私自身も同じ種類の問題を抱えており、ここで提案された解決策は非常に興味深いものであることがわかりましたが、次のような解決策が必要でした。

  1. 継承された関数も検出します。
  2. C ++ 11に対応していないコンパイラと互換性があります(したがってdecltypeはありません)

BOOSTディスカッションに基づいて、このようなものを提案している別のスレッドを見つけました。これは、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)
}

5

これを行うには、以下を使用する必要があります。

  1. メソッドが使用可能かどうかに応じて、戻り値の型が異なる関数テンプレートのオーバーロード
  2. type_traitsヘッダーのメタ条件に合わせて、オーバーロードから、true_typeまたはfalse_typeオーバーロードから
  3. 宣言true_type期待して過負荷intfalse_type:可変個引数パラメーターを利用するために期待して過負荷「オーバーロードの解決で省略記号変換の最も低い優先度を」
  4. true_type関数のテンプレート仕様を定義する際に使用しdeclvaldecltypeメソッド間の戻り値の型の違いやオーバーロードに関係なく関数を検出できるようにします

これのライブの例をここで見ることができますただし、以下でも説明します。

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ですtruevoid a::test()過負荷に対処するために特別な機能を作成する必要はありません。これvoid a::test(int)は受け入れられます)
  • decltype(hasTest<b>(0))::valueis trueint変換可能double int b::test(double)は受け入れられるため、戻り値のタイプとは関係ありません)
  • decltype(hasTest<c>(0))::valueis false(変換可能な型を受け入れるcという名前のメソッドがないため、これは受け入れられません)testint

このソリューションには2つの欠点があります。

  1. 関数のペアのメソッドごとの宣言が必要
  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インラインコードやメタプログラミングの目的のために。


3

非侵入型にするためserializeに、Koenig lookupのおかげで、シリアル化されるクラスまたはアーカイブクラスの名前空間に配置することもできます。詳細については、フリー関数オーバーライド用の名前空間を参照してください。:-)

任意の名前空間を開いて無料の関数を実装するのは単に間違っています。(たとえば、独自の型stdを実装するswapために名前空間を開く必要はありませんが、代わりにKoenigルックアップを使用する必要があります。)


3

検出器のイディオムが欲しいようです。上記の回答は、C ++ 11またはC ++ 14で機能するこのバリエーションです。

std::experimentalライブラリは、基本的にこれを行う機能を備えています。上記の例を書き直すと、次のようになります。

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

std :: experimentalを使用できない場合は、次のように基本的なバージョンを作成できます。

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

has_serialize_tは実際にはstd :: true_typeまたはstd :: false_typeのいずれかであるため、一般的なSFINAEイディオムのいずれかを介して使用できます。

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

または、過負荷解決でディスパッチを使用する:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

2

はい。2回目。これも気に入らなくても大丈夫です、もっとアイデアを探しています。

ハーブサッターの記事では、特性について説明しています。したがって、デフォルトのインスタンス化がフォールバック動作を持つ特性クラスを持つことができ、メンバー関数が存在するクラスごとに、特性クラスはメンバー関数の呼び出しに特化しています。Herbの記事では、これを行うためのテクニックについて言及しているため、コピーや貼り付けをあまり必要としません。

しかし、先ほど述べたように、おそらく、そのメンバーを実装する「タグ付け」クラスに関係する余分な作業を望まないでしょう。その場合、私は3番目の解決策を探しています...


ええと...私はこのソリューションを分析しました...私のフレームワークのユーザーにとっては少し高すぎると思います。(承知しました、認めます。私はストリーミングフレームワークを開発しており、iostreamを拡張するか、より簡単なものを書き換えるかを選択しています)
ugasoft

私の3番目の解決策は、SFINAEを使用することです。yrpの答えはすでにそれを言及しているので、私はそれについては触れません(私はまだそれを研究しているので:私はアイデアを知っていますが、悪魔は最終的にあなたのために機能しないのでない限り) 。:-)
クリスジェスターヤング

1

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;
}

うまくいけばどのように機能するか

AAaそしてB問題のクラスであり、Aa探しているメンバーを継承する特別なクラスです。

ではと対応のC ++ 11個のクラスの置換があります。また、テンプレートメタプログラミングを理解するために、SFINAE-sizeof-trickの基本を明らかにします。FooFindertrue_typefalse_type

TypeSink積分結果をシンクするために後で使用されるテンプレートの構造体であるsizeof型を形成するために、テンプレートのインスタンス化にオペレータを。

このmatch関数は、もう1つのSFINAEの種類のテンプレートであり、汎用のテンプレートがありません。したがって、引数の型が特殊化された型と一致する場合にのみインスタンス化できます。

両方のtest関数とenum宣言により、最終的に中心的なSFINAEパターンが形成されます。を返す省略記号を使用する一般的なものfalse_typeと、優先するより具体的な引数を持つ対応物があります。

インスタンス化することができるようにするにtestのテンプレート引数を持つ関数をTmatchその戻り値の型をインスタンス化するために必要とされるような機能は、インスタンス化されなければならないTypeSink引数を。注意点はつまり&U::foo、関数の引数に包まれて、されていないので、継承されたメンバーの検索はまだ行われ、テンプレート引数の特殊内から参照。


1

Facebookの愚かさを使用している場合、それらはすぐに使えるマクロです。

#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;
}

実装の詳細は前の回答と同じですが、ライブラリを使用する方が簡単です。


0

私も同様のニーズがあり、このSOに出くわしました。ここには多くの興味深い/強力な解決策が提案されていますが、特定の必要性には少し時間がかかります。クラスに正確なシグネチャを持つメンバー関数があるかどうかを検出します。だから私はいくつかの読書/テストを行い、興味があるかもしれない私のバージョンを思いつきました。検出します:

  • 静的メンバー関数
  • 非静的メンバー関数
  • 非静的メンバー関数const

正確な署名付き。署名を取得する必要がないので(より複雑なソリューションが必要になります)、これは私に適しています。基本的にはenable_if_tを使用していました

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

出力:

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1

0

jrok答え基づいて、ネストされたテンプレートクラスや関数の使用を避けました。

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

上記のマクロを以下のように使用できます。

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

提案は大歓迎です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.