Bir sınıfın belirli bir imzanın üye işlevine sahip olup olmadığını kontrol edin


135

Bir sınıfın belirli bir imzanın belirli bir üye işlevine sahip olup olmadığını tespit etmek için bir şablon numarası istiyorum.

Sorun, http://www.gotw.ca/gotw/071.htm burada alıntılanana benzer, ancak aynı değildir: Sutter'ın kitabının maddesinde, bir C sınıfının, belirli bir imza, aksi takdirde program derlemez. Benim problemimde, bir sınıfın bu işlevi varsa bir şeyler yapmam gerekiyor, yoksa "başka bir şey" yapmam gerekiyor.

Boost :: serileştirme ile benzer bir sorunla karşılaşıldı, ancak benimsedikleri çözümü beğenmedim: belirli bir üye işlevi tanımlamadığınız sürece (tanımlamanız gereken) varsayılan olarak belirli bir imzayla ücretsiz bir işlevi çağıran bir şablon işlevi ( belirli bir türden 2 parametre alan) belirli bir imza ile "serileştirme" durumunda, aksi takdirde bir derleme hatası meydana gelir. Bu, hem müdahaleci hem de müdahaleci olmayan serileştirmeyi uygulamaktır.

Bu çözümü iki nedenden dolayı beğenmiyorum:

  1. Müdahaleci olmamak için, boost :: serialization ad alanındaki global "serialize" işlevini geçersiz kılmalısınız, böylece ad alanı artırma ve ad alanı serileştirmeyi açmak için MÜŞTERİ KODUNUZDA var!
  2. Bu karmaşayı çözmek için yığın 10 ila 12 işlev çağrısı idi.

Bu üye işlevine sahip olmayan sınıflar için özel bir davranış tanımlamam gerekiyor ve varlıklarım farklı ad alanlarında (ve ben bir ad alanında tanımlanmış bir global işlevi diğerinde iken geçersiz kılmak istemiyorum)

Bu bulmacayı çözmem için bana bir ipucu verebilir misin?



@ R.MartinhoFernandes Ne tür bir cevap arıyorsunuz? Mike Kinghan'ın bu cevabı oldukça derinlemesine gidiyor ve C ++ 11 maddelerini kullanıyor.
jrok

R.MartinhoFernandes @ Belki bu sizin için modern bir versiyonu arıyoruz nedir?
Daniel Frey

Yanıtlar:


90

Sizi doğru anladığımdan emin değilim, ancak derleme zamanında işlevin varlığını tespit etmek için SFINAE'den yararlanabilirsiniz. Kodumdan örnek (sınıfın üye işlevi size_t used_memory () const olup olmadığını test eder).

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
buda ne böyle??? yasal c ++ kodu mu? "şablon <typename U, size_t (U :: *) () const>" yazabilir misiniz ?? ama ... bu harika ve yeni bir çözüm! Teşekkür ederim, yarın meslektaşlarımla daha iyi analiz edeceğim ... harika!
ugasoft

2
Örnekte "int_to_type" tanımı eksik. Açıkçası, cevaba bir şey eklemiyor, ancak bu, insanların hızlı bir kesip yapıştırdıktan sonra kodunuzu çalışırken görebileceği anlamına geliyor.
Richard Corden

2
İnt_to_type'ın basit bir tanımı şöyle olabilir: 'şablon <int N> struct int_to_type {};'. Çoğu uygulama, paramter N değerini ya bir enum'da ya da statik bir tamsayı sabitinde tutar (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
İnt_to_type yerine boost :: integral_constant'ı kullanmanız yeterlidir.
Vadim Ferderer

2
@JohanLundberg Bu bir işaretçi (statik olmayan) üye işlevidir. Örneğin size_t(std::vector::*p)() = &std::vector::size;,.
Monica'yı

133

İşte C ++ 11 özelliklerine dayanan olası bir uygulama. Miras alınmış olsa bile işlevi doğru bir şekilde algılar (Mike Kinghan'ın yanıtında gözlemlediği gibi, kabul edilen yanıttaki çözümün aksine ).

Bu parçacığın test ettiği işleve şu ad verilir 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;
};

Kullanımı:

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'nin "serialize" adlı bir yöntemi yoksa bu işe yarar mı? "Serialize" yöntemi olmasaydı nasıl yanlış bir değer döndüreceğini anlamıyorum.
Collin

1
@Collin bu durumda kontrolün ilk aşırı yüklemesi için şablon parametresinin değiştirilmesi başarısız olur ve aşırı yük setinden çıkarılır. False_type döndüren ikinciye geri döner. SFINAE prensibi nedeniyle bu bir derleyici hatası değildir.
jrok

1
@ elios264 Yok. Kontrol etmek istediğiniz her işlev için bir şablon yazmak için bir makro kullanabilirsiniz.
jrok

1
Kontrol bağımsız değişkeninin T veya T & yerine T * türünde olmasının belirli bir nedeni?
shibumi

1
Ama ya serializekendisi bir şablonu kabul ederse. serializeTam türü yazmadan varoluşu test etmenin bir yolu var mı ?
Hi-Angel

37

Bu derleme zamanlı üye işlevi iç gözlem sorusuna verilen kabul edilen yanıt, haklı olarak popüler olmasına rağmen, aşağıdaki programda gözlemlenebilecek bir engele sahiptir:

#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 ile oluşturulmuş program çıktıları 110- bizi bilgilendirdiğiniz olduğunu T = std::shared_ptr<int>gelmez değil sağlamak int & T::operator*() const.

Zaten bu konuya akıllı değilseniz std::shared_ptr<T>, başlıktaki tanımına bir göz atmak <memory>ışık tutacaktır. Bu uygulamada, std::shared_ptr<T>miras aldığı bir temel sınıftan türetilir operator*() const. Dolayısıyla, SFINAE<U, &U::operator*>işleci "bulmayı" oluşturan şablon somutlaştırma U = std::shared_ptr<T>gerçekleşmeyecektir, çünkü kendi başına std::shared_ptr<T>hiçbir operator*()hakkı yoktur ve şablon somutlaştırması "kalıtım yapmaz".

Bu engel, sadece Tüye işlevine sahip olup olmadığını tespit etmek için "sizeof () Trick" i kullanan iyi bilinen SFINAE yaklaşımını etkilemez mf(örneğin, bu cevaba ve yorumlara bakın). Ancak T::mfvar olduğunu tespit etmek çoğu zaman (genellikle?) Yeterince iyi değildir: İstenilen bir imzaya sahip olduğunu da belirlemeniz gerekebilir. Resimli tekniğin puan aldığı yer burasıdır. İstenen imzanın işaretli varyantı &T::mf, SFINAE sondasının başarılı olması için yerine getirilmesi gereken bir şablon türünün bir parametresine yazılmıştır . Ancak bu şablon örnekleme tekniği T::mf, miras alındığında yanlış yanıt verir .

Derleme zamanı iç gözlemi için güvenli bir SFINAE tekniği, SFINAE işlev şablonu çözümlemesinin bağlı olduğu bir türü somutlaştırmak için bir şablon bağımsız değişkeninin T::mfkullanımından kaçınmalıdır &T::mf. Bunun yerine, SFINAE şablon işlev çözümlemesi, yalnızca aşırı yüklenmiş SFINAE araştırma işlevinin bağımsız değişken türleri olarak kullanılan tam olarak uygun tür bildirimlerine bağlı olabilir.

Bu kısıtlamaya uyan soruya bir cevap olarak, E T::operator*() constrasgele Tve için derleme zamanı tespiti için örnekleyeceğim E. Aynı model, herhangi bir başka üye yöntemi imzasını araştırmak için gerekli değişiklikler yapılarak uygulanacaktır .

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

Bu çözümde, aşırı yüklenmiş SFINAE araştırma işlevi test()"özyinelemeli olarak çağrılır". (Elbette aslında çağrılmamıştır; yalnızca derleyici tarafından çözülen varsayımsal çağrıların dönüş türlerine sahiptir.)

En az bir ve en fazla iki bilgi noktasını araştırmamız gerekiyor:

  • Hiç T::operator*()var mı ? Değilse, işimiz bitti.
  • Var olduğu göz önüne alındığında T::operator*(), imzası E T::operator*() constmı?

Cevapları tek bir aramanın dönüş türünü değerlendirerek alıyoruz test(0,0). Bunu yapan:

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

Bu çağrı, /* SFINAE operator-exists :) */aşırı yüklemeye çözümlenebilir test()veya aşırı yüklemeye çözümlenebilir /* SFINAE game over :( */. Bu /* SFINAE operator-has-correct-sig :) */aşırı yüklemeyi çözemez , çünkü bu sadece bir argüman bekliyor ve biz ikisini geçiyoruz.

Neden ikiyi geçiyoruz? Çözünürlüğü hariç tutmaya zorlamak için /* SFINAE operator-has-correct-sig :) */. İkinci argümanın başka bir önemi yoktur.

Bu çağrı test(0,0)için irade kararlılığı /* SFINAE operator-exists :) */her ihtimale karşı ilk argüman 0 satifies olduğu aşırı yük, ilk parametresi tür decltype(&A::operator*)ile, A = T. 0 olması durumunda bu türü tatmin edecektir T::operator*.

Derleyicinin buna Evet dediğini varsayalım. Daha sonra devam /* SFINAE operator-exists :) */eder ve işlev çağrısının dönüş türünü belirlemesi gerekir, bu durumda decltype(test(&A::operator*))- başka bir çağrının dönüş türü test().

Bu sefer, var olduğunu bildiğimiz tek bir argüman geçiriyoruz &A::operator*, yoksa burada olmazdık. Bir çağrı test(&A::operator*)ya kudreti kararlılığının /* SFINAE operator-has-correct-sig :) */veya tekrar etmek kudreti Çözmek /* SFINAE game over :( */. Çağrı maç olacak /* SFINAE operator-has-correct-sig :) */durumda sadece &A::operator*tatmin olduğu aşırı yük, tek parametre türü E (A::*)() constile A = T.

Derleyici, T::operator*istenen imzaya sahipse burada Evet diyecek ve sonra tekrar aşırı yüklenmenin dönüş türünü değerlendirmesi gerekecektir. Artık "özyineleme" yok: öyle std::true_type.

Derleyici /* SFINAE operator-exists :) */çağrı için seçim yapmazsa test(0,0)veya çağrı /* SFINAE operator-has-correct-sig :) */ için seçim yapmazsa test(&A::operator*), her iki durumda da birlikte gider /* SFINAE game over :( */ve son dönüş türü olur std::false_type.

Burada, çeşitli vaka örneklerinde beklenen yanıtları üreten şablonu gösteren bir test programı bulunmaktadır (yine 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;
}

Bu fikirde yeni kusurlar var mı? Bir kez daha kaçındığı engele takılmadan daha genel hale getirilebilir mi?


16

İşte bazı kullanım parçacıkları: * Tüm bunlara ilişkin cesaret daha aşağıda

xBelirli bir sınıftaki üyeyi kontrol edin . Var, func, class, union veya enum olabilir:

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

Üye işlevini kontrol edin 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;

Üye değişkenini kontrol edin x:

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

Üye sınıfını kontrol edin x:

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

Üye sendikasını kontrol edin x:

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

Üye numaralandırmasını kontrol edin x:

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

xİmzadan bağımsız olarak herhangi bir üye işlevini kontrol edin :

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;

VEYA

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;

Ayrıntılar ve çekirdek:

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

Makrolar (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
Bu harika; bunu tek bir başlık dosyası kitaplığına koymak güzel olurdu.
Allan

12

Beklediğiniz üye işlevinin adını biliyorsanız, bu yeterli olmalıdır. (Bu durumda, üye işlevi yoksa bla işlevi somutlaştırmayı başaramaz (yine de çalışan bir işlevi yazmak zordur, çünkü işlev kısmi uzmanlık eksikliği vardır. Sınıf şablonlarını kullanmanız gerekebilir) Ayrıca, enable yapısını (ki enable_if'e benzer), üye olarak sahip olmasını istediğiniz işlev türüne göre de şablonlanabilir.

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
thaks! yrp tarafından önerilen çözüme benzer. Şablonun üye işlevler üzerinden oluşturulabileceğini bilmiyordum. Bu, bugün öğrendiğim yeni bir özellik! ... ve yeni bir ders: "asla c ++ konusunda uzman olduğunuzu söyleme" :)
ugasoft

7

İşte Mike Kinghan'ın cevabına daha basit bir bakış. Bu, devralınan yöntemleri algılayacaktır. Aynı zamanda tam imzayı da kontrol edecektir (jrok'un argüman dönüşümlerine izin veren yaklaşımının aksine).

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

Çalıştırılabilir örnek


Bu iyidir, ancak işlev hiçbir argüman
almazsa

Harika çalışıyor. Bu numarayı üye işlevlere argüman olmadan uygularken herhangi bir sorun yaşamadım.
JohnB

Bu benim için çok sayıda ve hiçbir yöntem argümanıyla, aşırı yüklemelerle ve kalıtımla dahil olmak üzere usingve temel sınıftan aşırı yüklemeleri getirmek için kullanımıyla iyi çalışıyor . Benim için MSVC 2015 ve Clang-CL ile çalışıyor. Ancak MSVC 2012 ile çalışmıyor.
steveire

5

Std :: is_member_function_pointer kullanabilirsiniz

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

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

16
Olmaz &A::foohiç söz konusuysa ve bir derleme hatası olması foohalinde hiç A? Orijinal soruyu, sadece adlandırılmış bir tür üyeye sahip olanlarla değil, herhangi bir girdi sınıfıyla çalışacakmış gibi okudum foo.
Jeff Walden

5

Ben de aynı türden bir problemle karşılaştım ve önerilen çözümleri burada çok ilginç buldum ... ancak şu özelliklere sahip bir çözüme ihtiyaç vardı:

  1. Devralınan işlevleri de algılar;
  2. C ++ 11'e hazır olmayan derleyicilerle uyumludur (dolayısıyla decltype yok)

BOOST tartışmasına dayalı olarak buna benzer bir şey öneren başka bir konu buldum . Yükseltme :: has_ ​​* sınıflarının modelini izleyerek, özellikler sınıfı için iki makro bildirimi olarak önerilen çözümün genelleştirilmesi .

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

Bu makrolar, aşağıdaki prototip ile bir nitelik sınıfına genişler:

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

Öyleyse, bunun dışında yapılabilecek tipik kullanım nedir?

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

Bunu başarmak için kullanmamız gerekecek:

  1. Yöntemin kullanılabilir olup olmadığına göre farklı dönüş türleriyle işlev şablonu aşırı yüklemesi
  2. Başlıktaki meta koşullara uygun olarak, aşırı yüklerimizden type_traitsbir true_typeveyafalse_type döndürmek isteyeceğiz
  3. Beyan true_typebir bekliyor aşırı intve false_type: yararlanmaya Değişkin Parametreleri bekliyor aşırı "aşırı yük çözünürlükte üç nokta dönüşümü için en düşük önceliği"
  4. İçin şablon şartname tanımlanmasında true_typebiz kullanacağız fonksiyonu declvalve decltypeyöntemleri arasında dönüş tipi farklılıkları veya aşırı işlevi bağımsız algılamak için bize izin

Bunun canlı bir örneğini burada görebilirsiniz . Ama aşağıda da açıklayacağım:

testDönüştürülebilir bir türü alan adlandırılmış bir işlevin varlığını kontrol etmek istiyorum int, sonra bu iki işlevi bildirmem gerekir:

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))::valueolduğu true(Not başa özel işlevler oluşturmak için gerek yoktur void a::test()aşırı, void a::test(int)kabul edilir)
  • decltype(hasTest<b>(0))::valueolduğu true(Çünkü inthiç aktarılabilecek double int b::test(double), kabul edilen dönüş tipinden bağımsız)
  • decltype(hasTest<c>(0))::valueolduğu false( cadında bir yöntemi yoktur testconvertible bir türünü kabul intbu kabul edilmez bunun)

Bu çözümün 2 dezavantajı vardır:

  1. Bir çift işlevin yöntem başına bildirimini gerektirir
  2. Özellikle benzer isimleri test etmek istiyorsak ad alanı kirliliği yaratır, örneğin, bir test()yöntemi test etmek isteyen bir işlevi ne adlandırırız ?

Bu nedenle, bu işlevlerin ayrıntılar ad alanında bildirilmesi veya ideal olarak yalnızca bir sınıfla kullanılacaksa, o sınıf tarafından özel olarak bildirilmesi önemlidir. Bu amaçla, bu bilgiyi özetlemenize yardımcı olacak bir makro yazdım:

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

Bunu şu şekilde kullanabilirsiniz:

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

Ardından çağrı details::test_int<a>::valueveya details::test_void<a>::valuedoğuracak trueveya falseiçi kodu veya meta-programlama amaçlar için kullanılabilir.


3

Müdahaleci serializeolmamak için, Koenig araması sayesinde serileştirilen sınıfın veya arşiv sınıfının ad alanına da girebilirsiniz . Daha fazla ayrıntı için Serbest İşlev Geçersiz Kılma için Ad Alanları konusuna bakın . :-)

Ücretsiz bir işlevi uygulamak için herhangi bir ad alanını açmak Basitçe Yanlıştır. (örneğin, kendi türlerinize stduygulamak swapiçin ad alanını açmanız gerekmiyor , bunun yerine Koenig aramasını kullanmalısınız.)


3

Detektör deyimini istiyor gibisin. Yukarıdaki cevaplar, bunun C ++ 11 veya C ++ 14 ile çalışan varyasyonlardır.

std::experimentalKütüphane esasen bunu özelliklere sahiptir. Yukarıdan bir örnek yeniden çalışıldığında şunlar olabilir:

#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 kullanamıyorsanız, aşağıdaki gibi basit bir sürüm yapılabilir:

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 gerçekten std :: true_type veya std :: false_type olduğundan, yaygın SFINAE deyimlerinden herhangi biri aracılığıyla kullanılabilir:

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

Veya aşırı yük çözümlemeli gönderimi kullanarak:

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

Tamam. İkinci deneme. Bunu da beğenmezsen sorun değil, daha fazla fikir arıyorum.

Herb Sutter'ın makalesi özelliklerden bahsediyor. Böylece, varsayılan somutlaştırması geri dönüş davranışına sahip olan bir özellik sınıfına sahip olabilirsiniz ve üye işlevinizin bulunduğu her sınıf için, nitelikler sınıfı üye işlevini çağırmak üzere özelleştirilir. Herb'ün makalesinde bunu yapmak için bir teknikten bahsettiğine inanıyorum, böylece çok fazla kopyalama ve yapıştırma içermiyor.

Dediğim gibi, belki de bu üyeyi uygulayan sınıfları "etiketleme" ile ilgili fazladan çalışmayı istemiyorsunuzdur. Bu durumda, üçüncü bir çözüme bakıyorum ...


eh ... Bu çözümü analiz ettim ... Sanırım çerçevemin kullanıcıları için biraz fazla pahalı. (tamam, kabul ediyorum, bir akış çerçevesi geliştiriyorum ve iostream'i genişletmek veya daha kolay bir şeyi yeniden yazmak arasında seçim
yapıyorum

Üçüncü çözümüm SFINAE kullanmak olacaktır. Yrp'nin cevabı zaten ondan bahsettiğinden, ona girmeyeceğim (çünkü hala üzerinde araştırma yapıyorum: fikri biliyorum, ama şeytan ayrıntılarda gizlidir), sonunda onun çözümü sizin için işe yaramazsa . :-)
Chris Jester-Young

1

C ++ 11 desteği olmadan ( decltype) bu işe yarayabilir:

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

Umarım nasıl çalışır

A, AaVe Bsöz konusu clases olan Aadevralır üye aradığımız o özel biri olmak.

Gelen ve muhabir C ++ 11 sınıfları için yerine vardır. Ayrıca şablon meta programlamanın anlaşılması için SFINAE-sizeof-trick'in temelini ortaya çıkarırlar.FooFindertrue_typefalse_type

Daha TypeSinksonra sizeofoperatörün integral sonucunu bir tür oluşturmak için bir şablon somutlaştırmaya batırmak için kullanılan bir şablon yapıdır.

matchFonksiyon genel meslektaşı olmadan bırakılır şablonun başka SFINAE türüdür. Bu nedenle, yalnızca bağımsız değişkeninin türü, uzmanlaştığı türle eşleşirse somutlaştırılabilir.

testEnum bildirimi ile birlikte her iki işlev de nihayet merkezi SFINAE modelini oluşturur. false_typeÖncelik almak için daha spesifik argümanlara sahip bir ve bir karşılık gelen bir üç nokta kullanan genel bir tane vardır .

Örneğini edebilmek testbir şablon argümanı ile işlevini T, matchdöndürme türü örneğini için gereklidir olarak işlev örneğinin başlatılması gerekir TypeSinkargüman. İkaz yani &U::foo, bir fonksiyon argüman sarılı olan, bir değil bu yüzden üye arama hala gerçekleşir miras, bir şablon argümanı uzmanlık içinden anılacaktır.


1

Eğer facebook çılgınlığını kullanıyorsanız, size yardımcı olmak için kutu makrosu dışında:

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

Uygulama ayrıntıları önceki cevapla aynı olsa da, kitaplık kullanımı daha basittir.


0

Benzer bir ihtiyacım vardı ve bu SO ile karşılaştım. Burada önerilen birçok ilginç / güçlü çözüm var, ancak sadece belirli bir ihtiyaç için biraz uzun: bir sınıfın kesin bir imzaya sahip üye işlevi olup olmadığını algılama. Bu yüzden biraz okuma / test yaptım ve ilgimi çekebilecek versiyonumu buldum. Şunları algılar:

  • statik üye işlevi
  • statik olmayan üye işlevi
  • statik olmayan üye işlevi sabiti

kesin bir imza ile. Herhangi bir imza yakalamam gerekmediğinden (bu daha karmaşık bir çözüm gerektirir), bu benim için uygun. Temelde enable_if_t kullandı .

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

Çıktı :

           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

Bina jrok 'ın cevabı , ben iç içe şablon sınıfları ve / veya işlevlerini kullanarak kaçınmışlardır.

#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

Yukarıdaki makroları aşağıdaki gibi kullanabiliriz:

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

Önerilere açığız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.