Sınıf üyesi işlevinin varlığı için geçici kontrol?


498

Bir sınıfta belirli bir üye işlevinin tanımlanmasına bağlı olarak davranışı değiştiren bir şablon yazmak mümkün müdür?

İşte ne yazmak istiyorum basit bir örnek:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Yani, eğer class Tetmiştir toString()tanımlanan, o zaman bunu kullanır; aksi taktirde değildir. Nasıl yapılacağını bilmediğim büyülü kısım "FUNCTION_EXISTS" kısmı.


6
Tabii ki, aşağıdaki şablon cevaplarının sadece derleme zamanı bilgileriyle çalıştığını söylemeye gerek yoktur, yani T'nin toString'e sahip olması gerekir. Eğer T, bir alt sınıfta geçerseniz yapar toString tanımlamak, ancak T yapar değil , sen toString tanımlı değil söylenecek.
Alice Purcell

Yanıtlar:


319

Evet, SFINAE ile belirli bir sınıfın belirli bir yöntem sağlayıp sağlamadığını kontrol edebilirsiniz. İşte çalışma kodu:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Ben sadece Linux ve gcc 4.1 / 4.3 ile test ettim. Farklı derleyiciler çalıştıran diğer platformlara taşınabilir olup olmadığını bilmiyorum.


18
Her ne kadar 'one' ve 'two' için aşağıdakileri kullandım: typedef char Small; platforma bağımlı değişken boyutu hakkında belirsizlik sağlamak için Big {char dummy [2];} sınıfı.
user23167

6
Dünyada sizeof (char) == sizeof (uzun) olan bir platform olduğundan şüphe duyuyorum
Nicola Bonelli

17
Tamamen emin değilim, ama bunun taşınabilir olduğunu düşünmüyorum. typeof bir GCC uzantısıdır, diğer derleyicilerde çalışmaz.
Leon Timmermans

56
typeof gerekli değildir - char [sizeof (& C :: helloworld)] de çalışır. Ve sizeof (long) == sizeof (char) karakterinden kaçınmak için {char [2]}; Bir boyut olmalı> = 2
MSalters

57
Değiştirin: Önemsiz, ama dışarı rakama biraz zaman aldı typeoftarafından decltypekullanılırken C ++ 0x -std = c ++ 0x yoluyla, örneğin.
saat

264

Bu soru eski, ancak C ++ 11 ile SFINAE'ye tekrar dayanan bir fonksiyon varlığını (veya tür olmayan bir üyenin varlığını) kontrol etmenin yeni bir yolunu bulduk:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Şimdi bazı açıklamalara bakalım. İlk olarak, içindeki ilk ifade geçerli değilse (aka, işlev mevcut değilse) işlevleri aşırı yük çözünürlüğünden hariç tutmak için SFINAE ifadesini kullanıyorum .serialize(_imp)decltype

void()Tüm bu işlevlerin dönüş türü yapmak için kullanılır void.

0Argüman tercih için kullanılan os << objher ikisi (literal mevcut olup olmadığını aşırı 0tiptedir intve bu ilk aşırı olarak iyi bir eşleşme).


Şimdi, muhtemelen bir özelliğin var olup olmadığını kontrol etmesini istiyorsunuz. Neyse ki, bunu yazmak kolaydır. Bununla birlikte, isteyebileceğiniz her farklı işlev adı için kendiniz bir özellik yazmanız gerektiğini unutmayın .

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Canlı örnek.

Ve açıklamalara geçelim. Birincisi, sfinae_trueyardımcı bir tiptir ve temel olarak yazı ile aynıdır decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Avantajı daha kısa olmasıdır.
Ardından, check-in işleminin başarısız olup olmamasına bağlı olarak, struct has_stream : decltype(...)her ikisinden std::true_typeveya sondan miras alır . Son olarak, nasıl yapılandıracağınızı bilmenize gerek kalmadan, geçtiğiniz her türden bir "değer" verir. Bu gibi, bir unevaluated bağlam içinde mümkün olduğu Not , ve diğerleri.std::false_typedecltypetest_stream
std::declvaldecltypesizeof


(Ve değerlendirilmemiş tüm bağlamlar) bu gelişmeyi elde ettiğinden decltype, bunun gerekli olmadığını unutmayın sizeof. Sadece decltypezaten bir tür sunuyor ve bu yüzden daha temiz. İşte sizeofaşırı yüklerden birinin versiyonu:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

intVe longparametreler aynı nedenle hala orada. Dizi işaretçisi, kullanılabilecek bir bağlam sağlamak sizeofiçin kullanılır.


4
Avantajı decltypeüzerinde sizeofdönen tür yıkıcı erişim haklarına sahip gerekmez ve dönüş türü ise örtük bir örnekleme neden olmaz, böylece geçici (işlev çağrıları için özel hazırlanmış kurallar tarafından tanıtılan olmadığını da sınıf şablonu örneklemesi).
Johannes Schaub - litb

5
Microsoft, C ++ derleyicisine henüz Expression SFINAE uygulamadı. Sadece bazı insanların zamandan tasarruf etmesine yardımcı olabileceğimi düşünün, çünkü bunun neden benim için çalışmadığını kafam karıştı. Güzel bir çözüm olsa da, Visual Studio'da kullanmak için sabırsızlanıyorum!
Jonathan

3
İlk örnek bağlantınız koptu
NathanOliver

1
Söylemek gerekir ki, static_assert(has_stream<X, char>() == true, "fail X");derlenecek ve char int dönüştürülebilir çünkü iddia etmeyecek, bu davranış istenmiyorsa ve tüm argüman türlerinin eşleşmesini istiyorsanız bunu nasıl başarabileceğimi bilmiyorum?
Gabriel

4
Decltype için iki argümanda olduğu gibi şaşkınsanız: decltype gerçekten sadece bir tane alır; virgül burada bir operatördür. Bkz. Stackoverflow.com/questions/16044514/…
André

159

C ++, SFINAE'nin bunun için kullanılmasına izin verir (C ++ 11 özellikleri ile bunun daha basit olduğuna dikkat edin, çünkü neredeyse keyfi ifadelerde genişletilmiş SFINAE'yi destekler - aşağıdaki ortak C ++ 03 derleyicileriyle çalışmak için tasarlanmıştır):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

yukarıdaki şablon ve makro, bir üye işlev işaretçisi türü ve gerçek üye işlev işaretçisi vererek bir şablonu başlatmaya çalışır. Türler sığmıyorsa, SFINAE şablonun yok sayılmasına neden olur. Bunun gibi kullanım:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Ancak toString, eğer dalda bu işlevi çağıramazsınız . derleyici her iki dalda geçerliliği kontrol edeceğinden, bu fonksiyonun bulunmadığı durumlarda başarısız olur. Bunun bir yolu SFINAE'yi bir kez daha kullanmaktır (enable_if takviyeden de alınabilir):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Kullanarak eğlenin. Bunun avantajı, aşırı yüklenmiş üye işlevleri ve ayrıca const üye işlevleri için de çalışmasıdır ( std::string(T::*)() consto zaman üye işlevi işaretçi türü olarak kullanmayı unutmayın !).


7
type_checkİmzaların tam olarak aynı olmasını sağlamak için nasıl kullanıldığını seviyorum . İmzalı bir yöntemin çağrılabileceği şekilde çağrılabilecek herhangi bir yöntemi eşleştirecek şekilde yapmanın bir yolu var mı Sign? (Örneğin, eğer Sign= std::string(T::*)(), std::string T::toString(int default = 42, ...)eşleşmeye izin verin .)
j_random_hacker

5
Bu konuda benim için hemen açık olmayan bir şey buluyorum, bu yüzden başkalarına yardımcı olması durumunda: chk tanımlanmıyor ve tanımlanmasına gerek yok! Sizeof operatörü, chk'nin çağrılmasına gerek kalmadan chk çıktısının boyutunu belirler.
SCFrench

3
@ deek0146: Evet, Tilkel bir tür olmamalıdır, çünkü işaretçi-yöntem-T işareti bildirimi SFINAE'ye tabi değildir ve herhangi bir sınıf dışı T için hata yapar. IMO'nun en kolay çözümü, is_classkontrol ile birleştirmektir artırmak.
Jan Hudec

2
toStringŞablonu esas alan bir işlevse bu işlemi nasıl yapabilirim ?
Frank

4
Bu (veya eşdeğeri bir şey) Boost'ta mı?
Dan Nissenbaum

89

C ++ 20 - requiresifadeler

C ++ 20 ile bir işlev varlığını kontrol etmek için yerleşik bir yol olan requiresifadeler gibi kavramlar ve çeşitli araçlar gelir . Onlarla optionalToStringişlevinizi aşağıdaki gibi yeniden yazabilirsiniz :

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20 - Tespit araç seti

N4502 , C ++ 17 standart kitaplığına dahil edilmesi için bir algılama araç seti önerir ve bu da onu en sonunda TS v2 kitaplık temelleri haline getirir. Muhtemelen standarda girmeyecek çünkü o zamandan beri requiresifadelerle destekleniyor, ancak yine de sorunu biraz zarif bir şekilde çözüyor. Araç kiti std::is_detected, üst kısmında tür veya işlev algılama metafonksiyonlarını kolayca yazmak için kullanılabilecek bazı metafonksiyonlar sunar . Nasıl kullanabileceğiniz aşağıda açıklanmıştır:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Yukarıdaki örneğin test edilmediğini unutmayın. Algılama araç seti henüz standart kitaplıklarda mevcut değildir, ancak teklif gerçekten ihtiyacınız varsa kolayca kopyalayabileceğiniz tam bir uygulama içerir. C ++ 17 özelliğiyle güzel oynuyor if constexpr:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14 - Boost.Hana

Boost.Hana görünüşe göre bu belirli örnek üzerine inşa ediyor ve belgelerinde C ++ 14 için bir çözüm sunuyor, bu yüzden doğrudan alıntı yapacağım:

[...] Hana is_valid, aynı şeyin çok daha temiz bir uygulamasını elde etmek için C ++ 14 jenerik lambdas ile birleştirilebilen bir fonksiyon sağlar :

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Bu bize has_toString, verilen ifadenin kendisine ilettiğimiz argüman üzerinde geçerli olup olmadığını döndüren bir işlev nesnesi ile ayrılır . Sonuç bir olarak döndürülür IntegralConstant, bu nedenle işlevin sonucu yine de bir tür olarak temsil edildiğinden, burada bir sorun yoktur. Şimdi, daha az ayrıntılı olmanın yanı sıra (bu bir astar!), Niyet çok daha net. Diğer avantajlar, has_toStringdaha yüksek mertebeden algoritmalara aktarılabilmesidir ve işlev kapsamında da tanımlanabilir, bu nedenle ad alanı kapsamını uygulama ayrıntılarıyla kirletmeye gerek yoktur.

Boost.TTI

Başka biraz deyimsel araç böyle bir kontrol yapmak için - rağmen daha az zarif - olduğu Boost.TTI Boost 1.54.0 tanıtılan,. Örneğin, makroyu kullanmanız gerekir BOOST_TTI_HAS_MEMBER_FUNCTION. Nasıl kullanabileceğiniz aşağıda açıklanmıştır:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Sonra boolbir SFINAE denetimi oluşturmak için kullanabilirsiniz.

açıklama

Makro , denetlenen türü ilk şablon parametresi olarak BOOST_TTI_HAS_MEMBER_FUNCTIONalan meta işlevi oluşturur has_member_function_toString. İkinci şablon parametresi, üye işlevinin dönüş türüne karşılık gelir ve aşağıdaki parametreler, işlev parametrelerinin türlerine karşılık gelir. Üye , sınıfın bir üye işlevine sahip olup olmadığını valueiçerir .trueTstd::string toString()

Alternatif olarak, has_member_function_toStringüye işlev işaretçisini şablon parametresi olarak alabilir. Nedenle, değiştirmek mümkündür has_member_function_toString<T, std::string>::valuetarafından has_member_function_toString<std::string T::* ()>::value.


1
03'den

@ZFY Boost.TTI'ın C ++ 03 ile de çalıştığını düşünüyorum, ancak bu en az zarif çözüm.
Morwenn

C ++ 20 çözümü gerçekten geçerli mi? Bunu istiyorum - ama g ++ ve msvc tarafından reddedildi - sadece clang tarafından kabul edildi.
Bernd Baumanns

cppreference değerinde okuyabilirsiniz: bir gereksinim ifadesi, gereksinimlerinde geçersiz türler veya ifadeler içeriyorsa ve şablon bir varlığın bildirimi içinde görünmüyorsa, program kötü biçimlendirilmiştir.
Bernd Baumanns

@BerndBaumanns Gerçekten mi? GCC bagajıyla çalışmayı başardım : godbolt.org/z/CBwZdE Belki haklısınız, sadece işe yaradığını kontrol ettim, ancak standart ifadelere göre yasal olup olmadığını kontrol etmedim.
Morwenn

56

Bu soru iki yaşında olmasına rağmen cevabımı eklemeye cesaret edeceğim. Umarım bir önceki tartışmasız mükemmel çözümü açıklığa kavuşturacaktır. Nicola Bonelli ve Johannes Schaub'un çok yararlı cevaplarını aldım ve IMHO, daha okunabilir, açık ve typeofuzatma gerektirmeyen bir çözümle birleştirdim :

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Gcc 4.1.2 ile kontrol ettim. Kredi esas olarak Nicola Bonelli ve Johannes Schaub'a gidiyor, bu yüzden cevabım size yardımcı olursa onlara oy verin :)


1
Sadece merak ediyorum, bu Konrad Rudolph'un aşağıdaki çözümünün yapmadığı bir şey yapıyor mu?
Alastair Irvine

3
@AlastairIrvine, bu çözüm içerideki tüm mantığı gizler, Konrad'ın bazı yüklerini kullanıcıya yükler. Kısa ve çok daha okunabilir olmasına rağmen, Konrad'ın çözümü, sahip olduğu her sınıf için ayrı bir şablon uzmanlığı gerektirir toString. Orada herhangi bir sınıfla çalışmak isteyen (boost gibi bir şey düşünün) genel bir kütüphane yazarsanız, kullanıcının bazı belirsiz şablonların ek uzmanlıklarını tanımlamasını istemek kabul edilemez olabilir. Bazen ortak arayüzü olabildiğince basit tutmak için çok karmaşık bir kod yazmak tercih edilir.
FireAphis

30

C ++ 11 için basit bir çözüm:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Güncelleme, 3 yıl sonra: (ve bu test edilmemiştir). Varlığı test etmek için, bunun işe yarayacağını düşünüyorum:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

4
Bu basit ve zariftir, ancak kesinlikle konuşmak OP'nin sorusuna cevap vermez: arayanın bir işlevin varlığını kontrol etmesini sağlamazsınız , her zaman sağlarsınız . Ama yine de güzel.
Adrian W

@AdrianW, iyi bir nokta. Cevabımı güncelledim. Yine de test etmedim
Aaron McDaid

Başka birine yardımcı olması durumunda template<typename>, değişken aşırı yüklenmeden önce bu işi yapamadım : çözünürlük için düşünülmüyordu.
Laboratorio Cobotica

Yine, bu geçersiz C ++ 11.
Peter

29

Bu tür özellikler için vardır. Ne yazık ki, bunların manuel olarak tanımlanması gerekir. Sizin durumunuzda aşağıdakileri hayal edin:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

5
statik sabitler yerine özellikler için enum tercih etmelisiniz: "Statik sabit üyeler, derleyiciyi statik üye için tanımlamayı başlatmaya ve ayırmaya zorlayan değerlerdir. Sonuç olarak, hesaplama artık salt derleme zamanı ile sınırlı değildir. " etki."
Özgür

5
"Numaralandırma değerleri lvalues ​​değildir (yani adresleri yoktur). Bu nedenle, onları" başvuru ile ilettiğinizde "statik bellek kullanılmaz. Neredeyse hesaplanmış değeri tam olarak geçirmiş gibi Bu düşünceler bizi numaralandırma değerlerini kullanmaya motive ediyor. "C ++ Şablonları: Tam Kılavuz
Özgür

22
Comptrol: Hayır, tamsayı tipi statik sabitler özel bir durum olduğu için alıntılanan pasaj burada geçerli değildir! Burada tam olarak bir enum gibi davranıyorlar ve tercih edilen yol. Eski numaralandırma kesmek sadece C ++ standardına uymayan derleyiciler için gerekliydi.
Konrad Rudolph

3
@Roger Pate: Pek sayılmaz. Burada “programda kullanılır” görünüşte “referans” ile eş anlamlıdır. Bu pasajın ve tüm modern C ++ derleyicilerinin uyguladığı pasajın hakim okuması, bildirmeye gerek kalmadan statik bir sabitin değerini alabilmenizdir (önceki cümle şunu söylüyor: “… üye integral sabit ifadelerde görünebilir ...”). Sen sadece onun adresini alırsak (açıkça aracılığıyla bunu tanımlamanız gerekir &T::xveya dolaylı bir referans olarak bağlama yoluyla).
Konrad Rudolph


25

Bu sorunun zaten uzun bir cevap listesi var, ancak Morwenn'in yorumunu vurgulamak istiyorum: C ++ 17 için gerçekten çok daha basit bir teklif var. Ayrıntılar için N4502'ye bakın, ancak bağımsız bir örnek olarak aşağıdakileri göz önünde bulundurun.

Bu kısım sabit kısımdır, bir başlığa koy.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

aradığınızı belirttiğiniz değişken kısmı vardır (bir tür, bir üye türü, bir işlev, bir üye işlevi vb.). OP durumunda:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

N4502'den alınan aşağıdaki örnek, daha ayrıntılı bir prob göstermektedir:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Yukarıda açıklanan diğer uygulamalarla karşılaştırıldığında, bu oldukça basittir: azaltılmış bir araç seti ( void_tve detect) yeterlidir, kıllı makrolara gerek yoktur. Ayrıca, önceki yaklaşımlara göre ölçülebilir derecede daha verimli (derleme zamanı ve derleyici bellek tüketimi) olduğu bildirilmiştir (bakınız N4502 ).

İşte canlı bir örnek . Clang ile iyi çalışıyor, ancak maalesef 5.1'den önceki GCC sürümleri void_t, beklendiği gibi çalışmamaya neden olan C ++ 11 standardının farklı bir yorumunu izledi . Yakk zaten geçici çözüm sağladı: Aşağıdaki tanımını kullanın void_t( parametre listesindeki void_t çalışır ancak dönüş türü olarak değil ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

Üye olmayan işlevleri algılayacak şekilde genişletmek mümkün müdür?
plasmacel

Evet elbette. Örneklere dikkatlice bakın: temel olarak bir ifade sağlar ve geçerli olup olmadığını kontrol edersiniz. Hiçbir şey bu ifadenin yalnızca üye işlev çağrısı ile ilgili olmasını gerektirmez.
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) geleceğin yolu ... Türler üzerindeki şeyleri tespit etmenin düzgün bir yolunu arıyordum ve N4502 de gitmek.
tlonuk

11

"Eğer X yaptıysam, derler miydiniz?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Sürekli has_to_stringşekilde has_to_string<T>::valueolan true, ancak ve ancak Tbir yöntemi vardır .toString, bu bağlamda 0 bağımsız değişken ile çağrılabilir.

Sonra, etiket gönderme kullanacağım:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

karmaşık SFINAE ifadelerinden daha sürdürülebilir olma eğilimindedir.

Kendinizi çok fazla yaparsanız, bu özellikleri bir makro ile yazabilirsiniz, ancak nispeten basittirler (her biri birkaç satır), belki de buna değmez:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

yukarıdakilerin yaptığı bir makro oluşturmaktır MAKE_CODE_TRAIT. İstediğiniz özelliğin adını ve türü test edebilecek bazı kodları iletirsiniz T. Böylece:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

Yukarıdaki özellikler sınıfını oluşturur.

Bir kenara, yukarıdaki teknik MS "SFINAE ifadesi" dediğimiz bir parçasıdır ve 2013 derleyici oldukça zor başarısız.

C ++ 1y'de aşağıdaki sözdiziminin mümkün olduğunu unutmayın:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

Bu, birçok C ++ özelliğini kötüye kullanan bir satır içi derleme koşullu daldır. Bunu yapmak muhtemelen buna değmez, çünkü (kodun satır içi olması) faydası maliyete değmez (kimsenin nasıl çalıştığını anlamanın yanında), ancak yukarıdaki çözümün varlığı ilgi çekici olabilir.


Bu özel durumları ele alıyor mu?
tower120

@ tower120 Denemek zorunda kalacağım: şablonların özel / genel / korumalı ile etkileşimi benim için biraz belirsiz. Ancak, nerede çağırdığınız önemli değildir has_to_string.
Yakk - Adam Nevraumont

ama biliyorsunuz, diğer taraftan bakarsanız ... Derived sınıfından korunan üyelere ulaşabiliriz. Belki bütün bu şeyleri İÇERİ sınıf içine koymak ve yapılardan constexpr işlevlerine dönüştürmek ...
tower120

İşte, bu coliru.stacked-crooked.com/a/ee94d16e7c07e093 bakın Sadece bunu yapamam
tower120

@ tower120 C ++ 1y çalışmasını sağlar: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

10

İşte bazı kullanım snippet'leri: * Tüm bunlar için cesaret daha aşağı

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 et 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 birliği 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 et 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
Eğer sig_check<func_sig, &T::func_name>serbest fonksiyon kontrolüne geçersek neden bir fikrin var mı : kontrol sig_check<func_sig, &func_name>etmek istediğimiz fonksiyonun isminden bahseden "bildirilmemiş tanımlayıcı" ile yapılamıyor mu? çünkü SFINAE'nin bir hata DEĞİL yapmasını beklerim, sadece üyeler için bunu yapar, neden ücretsiz işlevler için değil?
v.oddou

Serbest bir işlevin bir sınıf ya da yapı olmadığı gerçeğiyle bir ilgisi olacağını varsayıyorum. Bir üyenin varlığını çıkarmanın bu tekniği, C ++ 'daki çoklu kalıtım mekanizmasına odaklanır ve yalnızca kontrol ettiğiniz üyeyi gerçekten kontrol etmekte olduğunuz sınıfa karşı barındırmak amacıyla var olan bir saplama sınıfı arasındaki belirsizliği zorlar. Bu ilginç bir soru olsa da, bunu düşünmemişti. Diğer C ++ 11/14 üye kontrol tekniklerini kontrol edebilirsiniz, yeni standartta bazı akıllı şeyler gördüm.
Brett Rossier

Cevabınız için teşekkürler, bence kalıtım hakkında verdiğiniz intel'in daha derinlemesine kontrol edilmesi gerekebilir, çünkü şimdiye kadar, doğru ifade etme erişimini ifade etmeyecek bir ifade yapmak için SFINAE'ye güvenmek arasında herhangi bir ilişki görmedim şablon türü parametresindeki bir üye ve birden çok devralma. Ama tamamen C ++ 'da uzak kavramların bile birbirinden akabileceğine inanıyorum. Şimdi ücretsiz fonksiyonlar için bu soru ilginç: stackoverflow.com/questions/26744589 TC cevabı, "bildirilmemiş tanımlayıcı" dan kaçınmak için bir kukla beyan etme hilesi kullanıyor gibi görünüyor
v.oddou

8

(Yukarıdaki çözümlerin aksine) miras alınan üye işlevlerini de kontrol eden başka bir iş parçacığında buna bir cevap yazdım:

SFINAE devralınan üye işlevlerini kontrol edecek

İşte bu çözümden bazı örnekler:

Örnek 1:

Aşağıdaki imzalı bir üyeyi kontrol ediyoruz: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Lütfen yöntemin sabitliğini bile kontrol ettiğini ve ilkel tiplerle de çalıştığını unutmayın. (Yani has_const_begin<int>::valueyanlıştır ve derleme zamanı hatasına neden olmaz.)

ÖRNEK 2

Şimdi imzayı arıyoruz: void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

MyClass'ın varsayılan olarak yapılandırılabilir olması veya herhangi bir özel konsepti karşılaması gerekmediğini lütfen unutmayın. Teknik, şablon üyeleriyle de çalışır.

Bununla ilgili görüşlerini sabırsızlıkla bekliyorum.


7

Şimdi bu güzel bir bulmaca - harika bir soru!

İşte Nicola Bonelli'nin standart dışı typeofoperatöre dayanmayan çözümüne bir alternatif .

Ne yazık ki, GCC (MinGW) 3.4.5 veya Digital Mars 8.42n üzerinde çalışmaz, ancak MSVC'nin (VC6 dahil) tüm sürümlerinde ve Comeau C ++ üzerinde çalışır.

Daha uzun yorum bloğunun nasıl çalıştığı (veya çalışması gerektiği) hakkında ayrıntıları vardır. Dediği gibi, hangi davranışın standartlara uygun olduğundan emin değilim - bununla ilgili yorumları memnuniyetle karşılarım.


güncelleme - 7 Kasım 2008:

Bu kod sözdizimsel olarak doğru olsa da, MSVC ve Comeau C ++ 'nın gösterdiği davranış standarda uymuyor ( Leon Timmermans ve beni doğru yöne yönlendirdiği için lb sayesinde ). C ++ 03 standardı şunları söylüyor:

14.6.2 Bağımlı isimler [temp.dep]

Paragraf 3

Sınıf şablonunun veya sınıf şablonunun bir üyesinin tanımında, sınıf şablonunun temel sınıfı şablon parametresine bağlıysa, sınıfın tanımlanma noktasında nitelenmemiş ad araması sırasında temel sınıf kapsamı incelenmez şablon veya üye ya da sınıf şablonunun ya da üyesinin örneklenmesi sırasında.

Bu nedenle, MSVC veya Comeau , şablonun başlatıldığı sırada çağrı sitesinde ad araması gerçekleştirmenin toString()üye işlevini düşündüğünde , bu yanlıştır (aslında bu durumda aradığım davranış olsa da).TdoToString()

GCC ve Digital Mars'ın davranışları doğru görünüyor - her iki durumda da üye olmayan toString()işlev çağrıya bağlı.

Sıçanlar - Zekice bir çözüm bulmuş olabileceğimi düşündüm, bunun yerine birkaç derleyici hatasını ortaya çıkardım ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
Hayır, standartlara uygun değil, ancak -fpississive seçeneğini açarsanız GCC'de çalışacağını düşünüyorum.
Leon Timmermans

Yorumların çok fazla yer vermediğini biliyorum, ancak bunun neden standartlara uygun olmadığı hakkında bilgi verebilir misiniz? (Tartışmıyorum - merak ediyorum)
Michael Burr

Mike B: Standart 3.10 p15'te diyor: "Bir program, bir nesnenin depolanan değerine aşağıdaki türlerden farklı bir değer üzerinden erişmeye çalışırsa, davranış tanımsızdır" ve bu liste aslında yapmak.
Johannes Schaub - litb

4
neden bana başka bir yorum eklemiyor emin değilim: senin toString çağrı niteliksiz. bu yüzden her zaman serbest işlevi çağırır ve tabandaki işlev asla olmaz, çünkü taban sınıfı bir şablon türü parametresine bağlıdır.
Johannes Schaub - litb

@litb: İşaretçiler için teşekkürler. Burada 3.10 geçerli olduğunu sanmıyorum. DoToString () içindeki toString () çağrısı, "bir nesnenin depolanan değerine bir değer aracılığıyla erişmiyor". Ama 2. yorumun doğru. Cevabı güncelleyeceğim.
Michael Burr

6

Burada litb tarafından sunulan standart C ++ çözümü, yöntem bir temel sınıfta tanımlanırsa beklendiği gibi çalışmaz.

Bu durumu ele alan bir çözüm için bakınız:

Rusça: http://www.rsdn.ru/forum/message/2759773.1.aspx

Roman.Perepelitsa tarafından İngilizce çeviri: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

İnanılmaz derecede zekidir. Bununla birlikte, bu çözümün bir sorunu, test edilen tür temel sınıf olarak kullanılamayan bir derleyici ise (örneğin ilkel türler) derleyici hataları vermesidir.

Visual Studio'da, hiçbir bağımsız değişkeni olmayan bir yöntemle çalışıyorsanız, sizeof ifadesinde () sonucunu çıkarmak için bağımsız değişkenlerin etrafına fazladan bir () fazlalığının eklenmesi gerektiğini fark ettim.


Hmm, bu yazı fikirleri kullanarak kendi sürümünü geliştirmiş olması, fikrin başka bazı dezavantajları olduğunu buldum, bu yüzden kodu tekrar cevabımdan kaldırdı. Birincisi, tüm fonksiyonların hedef tipte herkese açık olması gerektiğidir. Bu nedenle, bir "f" işlevi olup olmadığını kontrol edemezsiniz: struct g { void f(); private: void f(int); };çünkü işlevlerden biri özeldir (bunun nedeni kodun çalışmasıdır using g::f;, bu da eğer ferişilebilir değilse başarısız olur ).
Johannes Schaub - litb

6

MSVC __if_exists ve __if_not_exists anahtar kelimelerine ( Doc ) sahiptir. Nicola'nın tip SF-SFINAE yaklaşımıyla birlikte OP'nin aradığı gibi GCC ve MSVC için bir kontrol oluşturabilirim.

Güncelleme: Kaynak burada bulunabilir


6

Bir Has_fookavram kontrolü yazarak SFINAE ve şablon kısmi uzmanlığı kullanan bir örnek :

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

Https://stackoverflow.com/a/264088/2712152'de sağlanan çözümü biraz daha genel hale getirmek için değiştirdim . Ayrıca, yeni C ++ 11 özelliklerinden hiçbirini kullanmadığından, eski derleyicilerle kullanabiliriz ve msvc ile de çalışmalıyız. Ancak derleyiciler varyasyon makroları kullandığından C99'un bunu kullanmasını sağlamalıdır.

Aşağıdaki makro, belirli bir sınıfın belirli bir typedef'e sahip olup olmadığını kontrol etmek için kullanılabilir.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Aşağıdaki makro, belirli bir sınıfın belirli bir üye işlevine sahip olup olmadığını belirli sayıda bağımsız değişkenle kontrol etmek için kullanılabilir.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Has_typedef ve has_mem_func denetimlerini gerçekleştirmek için yukarıdaki 2 makroyu şu şekilde kullanabiliriz:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

Şablon işlevleriyle üye işlevlerini desteklemek için bunu geliştirebilirsiniz. <Typename T> şablonunu <typename T, typename ... Args> olarak değiştirin, sonra değişken elg argümanlarına sahip bir kontrol yapısı oluşturmak için makro elipsinizdeki "Args ..." kullanabilirsiniz. Örneğin. "Void onNext (const T &)" yöntemini HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... );template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
algıla

4

Garip kimse bu sitede bir kez gördüğüm şu güzel numarayı önermedi:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

T'nin bir sınıf olduğundan emin olmalısınız. Foo arayışındaki belirsizlik bir ikame başarısızlığı gibi görünüyor. Gcc üzerinde çalışmasını sağladım, ancak standart olup olmadığından emin değilim.


3

Bazı "özelliklerin" tür tarafından desteklenip desteklenmediğini kontrol etmek için kullanılabilecek genel şablon:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

fooİmza ile uyumlu bir yöntem olup olmadığını kontrol eden şablondouble(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Örnekler

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


has_fooŞablon çağrısı satır içi satır içi yolu var mı is_supported. Ne istiyorum gibi bir şey çağırmaktır; std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. Bunun sebebi, has_foofonksiyonu kontrol edebilmek için kontrol etmek istediğim her farklı fonksiyon imzası için bir tanımlamak istiyorum.
CJCombrink

2

Bu çözüme ne dersiniz?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

Belirsiz toStringolduğu gibi aşırı yüklenirse başarısız olur &U::toString.
Yakk - Adam Nevraumont

@Yakk Bence bir oyuncu kadrosu bu sorunu çözebilir.
user1095108

2

Burada birçok yanıt var, ancak yeni c ++ özelliklerinden herhangi birini kullanmadan (yalnızca c ++ 98 özelliklerini kullanarak), gerçek yöntem çözünürlüğü siparişi gerçekleştiren bir sürümü bulamadım .
Not: Bu sürüm test edilmiş ve vc ++ 2013, g ++ 5.2.0 ve onlline derleyicisi ile çalışmaktadır.

Bu yüzden sadece sizeof () kullanan bir sürüm buldum:

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Canlı demo (genişletilmiş dönüş türü denetimi ve vc ++ 2010 geçici çözümü ile): http://cpp.sh/5b2vs

Kendim bulduğum için kaynak yok.

Canlı demosunu g ++ derleyicisinde çalıştırırken, 0 dizisi boyutlarına izin verildiğini, yani kullanılan static_assert'in başarısız olduğunda bile bir derleyici hatasını tetiklemeyeceğini lütfen unutmayın.
Sık kullanılan bir çözüm, makrodaki 'typedef' yerine 'extern' koymaktır.


Hayır, ama kendim beyan ediyorum ve rvalue kullanmıyor (kodumun üst kısmına bakın). Ya da kendinizi ikna edebilir ve c ++ 98 modunda canlı demosu deneyebilirsiniz. Not: static_assert c ++ 98 de değil, ancak geçici çözümler var (canlı demo)
user3296587

oh d'! kaçırdı. :-)
Ian Ni-Lewis

Statik öğeleriniz çalışmıyor. 0 yerine -1 dizisi boyutunu kullanmanız gerekir (koymayı deneyin static_assert(false);). Ben türetilmiş sınıfın belirli bir işlevi olup olmadığını belirlemek istiyorum CRTP ile bağlantılı olarak kullanıyordum - ki bu işe yaramazsa, ancak iddialarınız her zaman geçti. Ona biraz saç kaybettim.
domuz

G ++ kullandığınızı varsayıyorum. Lütfen unutmayın, gcc / g ++ mı sıfır boyutlu dizi (sağlayan bir uzantı gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

Operatörü aşırı yüklememek için bunu tekrar yazabilir misiniz? örneğin başka bir operatör seçin? Ayrıca, ad alanının has_awesome_member dışında herhangi bir şeyle kirlenmesinden kaçının?
einpoklum

1

Burada şablon üye işlevleri de dahil olmak üzere tüm olası üye işlev aşırı yüklenmelerini, muhtemelen varsayılan bağımsız değişkenlerle işleyen sürümüm. Bir üye işlev çağrısı yapılırken verilen arg türleriyle, birbirini dışlayan 3 senaryo ayırır: (1) geçerli veya (2) belirsiz veya (3) uygulanamaz. Örnek kullanım:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

Şimdi şöyle kullanabilirsiniz:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

İşte c ++ 11 ile yazılmış kod, ancak, (küçük ayarlarla) typeof uzantıları (örn. Gcc) olan c ++ 11 olmayanlara kolayca bağlayabilirsiniz. HAS_MEM makrosunu kendinizinkiyle değiştirebilirsiniz.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

Sen C ++ 14 tüm metaprogramming atlamak ve sadece bu kullanarak yazabilirsiniz fit::conditionalgelen Fit kütüphanede:

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Ayrıca işlevi doğrudan lambdalardan da oluşturabilirsiniz:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

Ancak, genel lambdaları desteklemeyen bir derleyici kullanıyorsanız, ayrı işlev nesneleri yazmanız gerekir:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
fitStandart dışında herhangi bir kütüphaneye bağımlı kalmamak için bunu yazmak ne kadar kolay ?
einpoklum

1

C ++ 20 ile aşağıdakileri yazabilirsiniz:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

İşte çalışma kodunun bir örneği.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptrile çağrıldığında intişlevi öncelikli olan ekstra argüman alan işlevi etkinleştirir .long0

İşlev uygulandığında dönen işlevler için aynı ilkeyi kullanabilirsiniz true.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

Benzer bir sorunum vardı:

Birkaç temel sınıftan türetilebilecek bir şablon sınıfı, bazıları belirli bir üyesi ve bazıları olmayan.

"Typeof" (Nicola Bonelli) cevabına benzer şekilde çözdüm, ancak decltype ile MSVS'de doğru bir şekilde derlenir ve çalışır:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

C ++ 17'de yapmanın bir başka yolu (boost: hana'dan esinlenilmiştir).

Bir kez yazın ve birçok kez kullanın. has_something<T>Tür özellikleri sınıfları gerektirmez .

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Misal

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
"Cevap tanımına ihtiyacımız yok" ... lütfen cevabınızı geliştirmek için biraz bilgilendirici açıklama ekleyin. Teşekkürler.
EvetThatIsMyName
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.