Periksa apakah kelas memiliki fungsi anggota dari tanda tangan yang diberikan

135

Saya meminta trik template untuk mendeteksi apakah kelas memiliki fungsi anggota tertentu dari tanda tangan yang diberikan.

Masalahnya mirip dengan yang dikutip di sini http://www.gotw.ca/gotw/071.htm tetapi tidak sama: dalam item buku Sutter ia menjawab pertanyaan bahwa kelas C HARUS MENYEDIAKAN fungsi anggota dengan tanda tangan tertentu, jika tidak program tidak dapat dikompilasi. Dalam masalah saya, saya perlu melakukan sesuatu jika kelas memiliki fungsi itu, yang lain lakukan "sesuatu yang lain".

Masalah serupa juga dihadapi oleh boost :: serialisasi tetapi saya tidak suka solusi yang mereka adopsi: fungsi templat yang memanggil fungsi bebas secara default (yang harus Anda tetapkan) dengan tanda tangan tertentu kecuali jika Anda menetapkan fungsi anggota tertentu ( dalam kasus mereka "membuat cerita bersambung" yang mengambil 2 parameter dari jenis tertentu) dengan tanda tangan tertentu, jika tidak maka kesalahan kompilasi akan terjadi. Yaitu untuk mengimplementasikan serialisasi baik intrusif maupun non-intrusif.

Saya tidak suka solusi itu karena dua alasan:

  1. Agar tidak mengganggu, Anda harus mengganti fungsi "serialisasi" global yang ada di boost :: serialisasi namespace, sehingga Anda MEMILIKI DALAM KODE KLIEN ANDA untuk membuka boost namespace dan serialisasi namespace!
  2. Tumpukan untuk menyelesaikan kekacauan itu adalah 10 hingga 12 pemanggilan fungsi.

Saya perlu mendefinisikan perilaku khusus untuk kelas yang tidak memiliki fungsi anggota, dan entitas saya berada di dalam ruang nama yang berbeda (dan saya tidak ingin mengganti fungsi global yang didefinisikan dalam satu namespace sementara saya di yang lain)

Bisakah Anda memberi saya petunjuk untuk memecahkan teka-teki ini?

ugasoft
sumber
1
Pertanyaan serupa: stackoverflow.com/questions/257288
Johannes Schaub - litb
@ R.MartinhoFernandes Apa jawaban yang Anda cari? Jawaban oleh Mike Kinghan ini cukup mendalam dan menggunakan C ++ 11 hal.
jrok
@ R.MartinhoFernandes Mungkin ini versi modern yang Anda cari?
Daniel Frey

Jawaban:

90

Saya tidak yakin apakah saya mengerti Anda dengan benar, tetapi Anda dapat mengeksploitasi SFINAE untuk mendeteksi keberadaan fungsi pada waktu kompilasi. Contoh dari kode saya (menguji apakah kelas memiliki fungsi anggota 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>());
}
yrp
sumber
14
apa ini ??? apakah kode c ++ legal? dapatkah Anda menulis "template <typename U, size_t (U :: *) () const>" ?? tapi ... ini solusi hebat dan baru! Saya berterima kasih, saya akan menganalisis besok dengan rekan-rekan saya lebih baik ... bagus!
ugasoft
2
Contohnya tidak memiliki definisi 'int_to_type'. Jelas itu tidak menambah jawaban, tetapi itu berarti bahwa orang dapat melihat kode Anda dalam tindakan setelah dipotong cepat & tempel.
Richard Corden
2
Definisi sederhana int_to_type dapat berupa: 'templat <int N> struct int_to_type {};'. Banyak implementasi menjaga nilai N paramter baik dalam enum atau yang lain dalam konstanta integer statis (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas
2
Cukup gunakan boost :: integral_constant daripada int_to_type.
Vadim Ferderer
2
@JohanLundberg Ini adalah fungsi anggota pointer-to- (non-statis-). Sebagai contoh size_t(std::vector::*p)() = &std::vector::size;,.
Pasang kembali Monica
133

Berikut ini adalah implementasi yang mungkin mengandalkan fitur C ++ 11. Itu benar mendeteksi fungsi bahkan jika itu diwarisi (tidak seperti solusi dalam jawaban yang diterima, seperti Mike Kinghan mengamati dalam jawabannya ).

Fungsi tes potongan ini disebut 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;
};

Pemakaian:

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
jrok
sumber
Apakah ini berfungsi jika Y tidak memiliki metode yang disebut "cerita bersambung"? Saya tidak melihat bagaimana itu akan mengembalikan nilai yang salah jika metode "serialize" tidak ada.
Collin
1
@Collin dalam kasus itu, penggantian parameter templat gagal karena kelebihan pemeriksaan pertama dan dibuang dari kelebihan pengaturan. Itu jatuh kembali ke yang kedua yang mengembalikan false_type. Ini bukan kesalahan kompilator karena prinsip SFINAE.
jrok
1
@ elios264 Tidak ada. Anda bisa menggunakan makro untuk menulis templat untuk setiap fungsi yang ingin Anda periksa.
jrok
1
Ada alasan khusus mengapa argumen untuk pemeriksaan adalah tipe T * daripada T atau T &?
shibumi
1
Tetapi bagaimana jika serializeitu sendiri menerima templat. Apakah ada cara untuk menguji serializekeberadaan tanpa mengetik jenis yang tepat?
Hi-Angel
37

Jawaban yang diterima untuk pertanyaan introspeksi fungsi-anggota ini, meskipun populer, memiliki hambatan yang dapat diamati dalam program berikut:

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

Dibangun dengan GCC 4.6.3, program keluaran 110- memberitahu kita bahwa T = std::shared_ptr<int>tidak tidak memberikan int & T::operator*() const.

Jika Anda belum bijaksana dengan gotcha ini, maka melihat definisi std::shared_ptr<T>di header <memory>akan menjelaskan. Dalam implementasi itu, std::shared_ptr<T>diturunkan dari kelas dasar dari mana ia mewarisi operator*() const. Jadi contoh template SFINAE<U, &U::operator*>yang merupakan "menemukan" operator untuk U = std::shared_ptr<T>tidak akan terjadi, karena std::shared_ptr<T>tidak ada operator*() memiliki haknya sendiri dan instantiasi template tidak "melakukan pewarisan".

Halangan ini tidak mempengaruhi pendekatan SFINAE yang terkenal, menggunakan "Trik sizeof ()", untuk mendeteksi hanya apakah Tmemiliki beberapa fungsi anggota mf(lihat misalnya jawaban dan komentar ini). Tetapi membangun yang T::mfada sering (biasanya?) Tidak cukup baik: Anda mungkin juga perlu memastikan bahwa itu memiliki tanda tangan yang diinginkan. Di situlah skor teknik digambarkan. Varian terarah dari tanda tangan yang diinginkan tertulis dalam parameter tipe templat yang harus dipenuhi &T::mfagar probe SFINAE berhasil. Namun teknik cetakan template ini memberikan jawaban yang salah ketika T::mfdiwariskan.

Teknik SFINAE yang aman untuk introspeksi kompilasi dari T::mfharus menghindari penggunaan di &T::mfdalam argumen templat untuk membuat instantiate jenis di mana resolusi templat fungsi SFINAE bergantung. Sebagai gantinya, resolusi fungsi templat SFINAE hanya dapat bergantung pada deklarasi tipe yang tepat terkait yang digunakan sebagai tipe argumen dari fungsi probe SFINAE yang kelebihan beban.

Sebagai jawaban atas pertanyaan yang mematuhi batasan ini, saya akan mengilustrasikan untuk pendeteksian waktu E T::operator*() const, untuk arbitrer Tdan E. Pola yang sama akan berlaku mutatis mutandis untuk menyelidiki tanda tangan metode anggota lainnya.

#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? */
};

Dalam solusi ini, fungsi probe SFINAE yang kelebihan beban test()"dipanggil secara rekursif". (Tentu saja itu tidak benar-benar dipanggil sama sekali; itu hanya memiliki jenis pengembalian pemanggilan hipotetis diselesaikan oleh kompiler.)

Kami perlu menyelidiki setidaknya satu dan paling banyak dua poin informasi:

  • Apakah T::operator*()ada sama sekali? Jika tidak, kita sudah selesai.
  • Mengingat T::operator*()ada, apakah tanda tangannya E T::operator*() const?

Kami mendapatkan jawaban dengan mengevaluasi tipe kembali dari satu panggilan ke test(0,0). Itu dilakukan oleh:

    typedef decltype(test<T>(0,0)) type;

Panggilan ini mungkin diselesaikan dengan /* SFINAE operator-exists :) */kelebihan test(), atau mungkin memutuskan untuk /* SFINAE game over :( */kelebihan. Itu tidak dapat mengatasi /* SFINAE operator-has-correct-sig :) */kelebihan, karena yang satu hanya mengharapkan satu argumen dan kami melewati dua.

Mengapa kita melewati dua? Cukup dengan memaksa resolusi untuk dikecualikan /* SFINAE operator-has-correct-sig :) */. Argumen kedua tidak memiliki arti lain.

Panggilan ke ini test(0,0)akan diselesaikan untuk /* SFINAE operator-exists :) */berjaga-jaga jika argumen pertama 0 mengesahkan jenis parameter pertama dari kelebihan itu, yaitu decltype(&A::operator*), dengan A = T. 0 akan memenuhi jenis itu untuk berjaga-jaga jika T::operator*ada.

Anggap saja kompiler mengatakan Ya untuk itu. Maka itu akan dengan /* SFINAE operator-exists :) */dan perlu menentukan jenis kembali dari panggilan fungsi, yang dalam hal ini adalah decltype(test(&A::operator*))- jenis kembali dari panggilan lain untuk test().

Kali ini, kami hanya menyampaikan satu argumen &A::operator*, yang sekarang kami tahu ada, atau kami tidak akan berada di sini. Panggilan ke test(&A::operator*)mungkin diselesaikan baik ke /* SFINAE operator-has-correct-sig :) */atau lagi ke mungkin diselesaikan /* SFINAE game over :( */. Panggilan akan cocok untuk /* SFINAE operator-has-correct-sig :) */berjaga-jaga jika &A::operator*memenuhi jenis parameter tunggal dari kelebihan itu, yaitu E (A::*)() const, dengan A = T.

Kompiler akan mengatakan Ya di sini jika T::operator*memiliki tanda tangan yang diinginkan, dan sekali lagi harus mengevaluasi jenis pengembalian kelebihan. Tidak ada lagi "rekursi" sekarang: itu std::true_type.

Jika kompiler tidak memilih /* SFINAE operator-exists :) */untuk panggilan test(0,0)atau tidak memilih /* SFINAE operator-has-correct-sig :) */ untuk panggilan test(&A::operator*), maka dalam kedua kasus itu berjalan dengan /* SFINAE game over :( */dan jenis pengembalian akhir adalah std::false_type.

Berikut adalah program pengujian yang menunjukkan templat yang menghasilkan jawaban yang diharapkan dalam beragam sampel kasus (GCC 4.6.3 lagi).

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

Apakah ada kekurangan baru dalam ide ini? Bisakah itu dibuat lebih umum tanpa sekali lagi jatuh dari halangan yang dihindarinya?

Mike Kinghan
sumber
16

Berikut beberapa cuplikan penggunaan: * Nyali untuk semua ini lebih jauh ke bawah

Periksa anggota xdi kelas yang diberikan. Bisa berupa var, func, class, union, atau enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Periksa fungsi anggota 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;

Periksa variabel anggota x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Periksa kelas anggota x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Periksa serikat anggota x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Periksa enum anggota x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Periksa fungsi anggota apa pun xtanpa tanda tangan:

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;

ATAU

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;

Detail dan inti:

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

Makro (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)
Brett Rossier
sumber
1
Ini bagus; alangkah baiknya untuk menempatkan ini di perpustakaan file header tunggal.
Allan
12

Ini harus cukup, jika Anda tahu nama fungsi anggota yang Anda harapkan. (Dalam hal ini, fungsi bla gagal untuk instantiate jika tidak ada fungsi anggota (menulis yang berfungsi tetap sulit karena ada kekurangan spesialisasi fungsi parsial. Anda mungkin perlu menggunakan templat kelas) Juga, memungkinkan struct (yang mirip dengan enable_if) juga dapat digunakan pada jenis fungsi yang Anda inginkan sebagai anggota.

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);
}
coppro
sumber
4
terima kasih! ini mirip dengan solusi yang diusulkan oleh yrp. Saya tidak tahu bahwa templat dapat digabungkan dengan fungsi anggota. Itu fitur baru yang saya pelajari hari ini! ... dan pelajaran baru: "tidak pernah mengatakan kamu ahli dalam c ++" :)
ugasoft
7

Ini jawaban sederhana dari jawaban Mike Kinghan. Ini akan mendeteksi metode yang diwarisi. Itu juga akan memeriksa tanda tangan yang tepat (tidak seperti pendekatan jrok yang memungkinkan konversi argumen).

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, "");

Contoh runnable

Valentin Milea
sumber
Ini bagus, tetapi tidak akan berfungsi jika fungsi tidak mengambil argumen
Triskeldeian
Itu bekerja dengan baik. Saya tidak punya masalah menerapkan trik ini untuk fungsi anggota tanpa argumen.
JohnB
Ini bekerja dengan baik bagi saya dengan beberapa argumen metode dan tanpa metode, termasuk dengan kelebihan, dan termasuk dengan warisan, dan dengan penggunaan usinguntuk membawa kelebihan dari kelas dasar. Ini bekerja untuk saya di MSVC 2015 dan dengan Dentang-CL. Namun tidak bekerja dengan MSVC 2012.
steveire
5

Anda dapat menggunakan std :: is_member_function_pointer

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

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Yochai Timmer
sumber
16
Tidak &A::fooakan ada kesalahan kompilasi jika tidak ada foosama sekali di dalamnya A? Saya membaca pertanyaan asli yang seharusnya bekerja dengan kelas input apa pun, bukan hanya yang memiliki nama anggota foo.
Jeff Walden
5

Datang dengan masalah yang sama sendiri, dan menemukan solusi yang diusulkan di sini sangat menarik ... tetapi memiliki persyaratan untuk solusi yang:

  1. Mendeteksi fungsi yang diwarisi juga;
  2. Kompatibel dengan kompiler non C ++ 11 siap (jadi tidak ada jenis tulisan)

Temukan utas lain yang mengusulkan sesuatu seperti ini, berdasarkan pada diskusi BOOST . Berikut adalah generalisasi dari solusi yang diusulkan sebagai dua deklarasi makro untuk kelas ciri, mengikuti model kelas 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__

Makro ini diperluas ke kelas ciri dengan prototipe berikut:

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

Jadi, apa penggunaan khas yang bisa dilakukan dari ini?

// 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)
}
S. Paris
sumber
5

Untuk mencapai ini kita harus menggunakan:

  1. Templat fungsi berlebih dengan tipe pengembalian yang berbeda sesuai dengan apakah metode tersedia
  2. Sesuai dengan meta-conditional di type_traitsheader, kami ingin mengembalikan a true_typeataufalse_type dari kelebihan kami
  3. Nyatakan true_typekelebihan ekspektasi intdan false_typekelebihan ekspektasi Parameter Variadik untuk dieksploitasi: "Prioritas terendah konversi elipsis dalam resolusi kelebihan beban"
  4. Dalam mendefinisikan spesifikasi templat untuk true_typefungsi yang akan kita gunakan declvaldan decltypememungkinkan kita untuk mendeteksi fungsi yang tidak bergantung pada perbedaan jenis pengembalian atau kelebihan antar metode

Anda dapat melihat contoh langsung dari ini di sini . Tetapi saya juga akan menjelaskannya di bawah ini:

Saya ingin memeriksa keberadaan fungsi bernama testyang mengambil tipe convertible int, maka saya perlu mendeklarasikan dua fungsi ini:

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))::valueadalah true(Catatan tidak perlu membuat fungsionalitas khusus untuk menangani void a::test()kelebihan beban, void a::test(int)ini diterima)
  • decltype(hasTest<b>(0))::valueis true(Karena intdapat dikonversi ke double int b::test(double)diterima, terlepas dari jenis pengembalian)
  • decltype(hasTest<c>(0))::valueis false( ctidak memiliki metode bernama testyang menerima tipe convertible dari sini intkarena ini tidak diterima)

Solusi ini memiliki 2 kelemahan:

  1. Membutuhkan deklarasi per metode untuk sepasang fungsi
  2. Menciptakan polusi namespace terutama jika kita ingin menguji untuk nama yang mirip, misalnya apa yang akan kita beri nama fungsi yang ingin menguji suatu test()metode?

Jadi, penting bahwa fungsi-fungsi ini dideklarasikan dalam namespace detail, atau idealnya jika hanya digunakan dengan kelas, mereka harus dideklarasikan secara pribadi oleh kelas itu. Untuk itu saya telah menulis makro untuk membantu Anda abstrak informasi ini:

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

Anda bisa menggunakan ini seperti:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Selanjutnya memanggil details::test_int<a>::valueatau details::test_void<a>::valueakan menghasilkan trueatau falseuntuk tujuan inline code atau meta-programming.

Jonathan Mee
sumber
3

Agar tidak mengganggu, Anda juga dapat memasukkan serializenamespace dari kelas yang diserialisasi, atau dari kelas arsip, berkat pencarian Koenig . Lihat Namespace untuk Pengubahan Fungsi Gratis untuk detail lebih lanjut. :-)

Membuka setiap namespace yang diberikan untuk mengimplementasikan fungsi gratis adalah Simply Wrong. (mis. Anda tidak seharusnya membuka namespace stduntuk diterapkan swapuntuk tipe Anda sendiri, tetapi sebaiknya gunakan pencarian Koenig.)

Chris Jester-Young
sumber
3

Anda tampaknya menginginkan idiom detektor. Jawaban di atas adalah variasi untuk ini yang bekerja dengan C ++ 11 atau C ++ 14.

The std::experimentalperpustakaan memiliki fitur yang melakukan dasarnya ini. Mengolah contoh dari atas, mungkin:

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

Jika Anda tidak dapat menggunakan std :: eksperimental, versi yang belum sempurna dapat dibuat seperti ini:

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;

Karena has_serialize_t benar-benar std :: true_type atau std :: false_type, ini dapat digunakan melalui salah satu idiom SFINAE yang umum:

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

Atau dengan menggunakan pengiriman dengan resolusi kelebihan:

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);
}
lar
sumber
2

Baik. Percobaan kedua. Tidak apa-apa jika Anda tidak menyukai yang ini juga, saya mencari lebih banyak ide.

Artikel Herb Sutter berbicara tentang sifat-sifat. Jadi Anda dapat memiliki kelas sifat yang instantiasi defaultnya memiliki perilaku mundur, dan untuk setiap kelas di mana fungsi anggota Anda ada, maka kelas sifat khusus untuk menjalankan fungsi anggota. Saya percaya artikel Herb menyebutkan teknik untuk melakukan ini sehingga tidak melibatkan banyak menyalin dan menempel.

Seperti yang saya katakan, mungkin Anda tidak ingin pekerjaan tambahan yang terlibat dengan "penandaan" kelas yang mengimplementasikan anggota itu. Dalam hal ini, saya sedang mencari solusi ketiga ....

Chris Jester-Young
sumber
eh ... Saya sudah menganalisis solusi ini ... Saya pikir ini agak terlalu mahal untuk pengguna kerangka kerja saya. (ok, saya akui, saya sedang mengembangkan kerangka streaming dan saya memilih antara memperluas iostream atau menulis ulang sesuatu yang lebih mudah)
ugasoft
Solusi ketiga saya adalah menggunakan SFINAE. Karena jawaban yrp sudah menyebutkannya, aku tidak akan membahasnya (karena aku masih merisetnya: aku tahu idenya, tapi iblis ada dalam perinciannya), kecuali jika solusinya tidak bekerja untukmu pada akhirnya . :-)
Chris Jester-Young
1

Tanpa dukungan C ++ 11 ( decltype) ini mungkin berfungsi:

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

Semoga ini berhasil

A, Aa dan Bapakah klas yang dimaksud, Aaadalah yang istimewa yang mewarisi anggota yang kami cari.

Dalam FooFinder dalam true_typedan false_typemerupakan pengganti koresponden C ++ 11 kelas. Juga untuk memahami pemrograman meta templat, mereka mengungkapkan dasar dari trik-ukuran SFINAE.

Itu TypeSink adalah kerangka templat yang digunakan kemudian untuk menenggelamkan hasil integral dari sizeofoperator ke dalam contoh kerangka untuk membentuk suatu jenis.

The matchfungsi lain SFINAE jenis template yang dibiarkan tanpa rekan generik. Oleh karena itu hanya dapat dipakai jika jenis argumennya cocok dengan jenis yang dikhususkan untuknya.

Keduanya test fungsi bersama dengan deklarasi enum akhirnya membentuk pola SFINAE pusat. Ada yang umum menggunakan elipsis yang mengembalikan false_typedan rekan dengan argumen yang lebih spesifik untuk diutamakan.

Untuk dapat instantiate testfungsi dengan argumen templat T, matchfungsi harus instantiated, karena jenis kembalinya diperlukan untuk instantiate TypeSinkargumen. Peringatan adalah bahwa &U::foo, yang dibungkus dalam argumen fungsi, tidak dirujuk dari dalam spesialisasi argumen templat, sehingga pencarian anggota yang diwariskan masih berlangsung.

Kamajii
sumber
1

Jika Anda menggunakan kebodohan Facebook, makro kebodohan di luar kotak untuk membantu Anda:

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

Meskipun detail implementasi sama dengan jawaban sebelumnya, menggunakan perpustakaan lebih sederhana.

prehistoricpenguin
sumber
0

Saya memiliki kebutuhan yang sama dan menemukan SO ini. Ada banyak solusi menarik / kuat yang diusulkan di sini, meskipun agak lama hanya untuk kebutuhan spesifik: mendeteksi apakah kelas memiliki fungsi anggota dengan tanda tangan yang tepat. Jadi saya melakukan beberapa pembacaan / pengujian dan muncul dengan versi saya yang mungkin menarik. Ini mendeteksi:

  • fungsi anggota statis
  • fungsi anggota tidak statis
  • konstanta fungsi anggota tidak statis

dengan tanda tangan yang tepat. Karena saya tidak perlu menangkap tanda tangan apa pun (yang membutuhkan solusi yang lebih rumit), yang ini cocok untuk saya. Ini pada dasarnya menggunakan 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;
}

Output:

           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
ctNGUYEN
sumber
0

Membangun jrok 's jawaban , saya menghindari menggunakan kelas template yang bersarang dan / atau fungsi.

#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

Kita dapat menggunakan makro di atas seperti di bawah ini:

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

Saran diterima.

debashish.ghosh
sumber