Controllo basato su modelli per l'esistenza di una funzione membro della classe?


499

È possibile scrivere un modello che cambia comportamento a seconda che una determinata funzione membro sia definita su una classe?

Ecco un semplice esempio di ciò che vorrei scrivere:

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

Quindi, se class Tè stato toString()definito, lo utilizza; altrimenti no. La parte magica che non so fare è la parte "FUNCTION_EXISTS".


6
Ovviamente è ovvio che le risposte al modello qui sotto funzionano solo con informazioni in fase di compilazione, ovvero T deve avere ToString. Se si passa in una sottoclasse di T che fa definire toString, ma non T non , si sarà detto toString non è definito.
Alice Purcell,

Possibile duplicato Come verificare se esiste un nome membro (variabile o funzione) in una classe, con o senza specificare il tipo? , poiché copre un problema più ampio con C ++ da 03 a C ++ 1y.
iammilind,

Risposte:


319

Sì, con SFINAE puoi verificare se una determinata classe fornisce un determinato metodo. Ecco il codice di lavoro:

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

L'ho appena testato con Linux e gcc 4.1 / 4.3. Non so se sia portatile su altre piattaforme che eseguono compilatori diversi.


18
Tuttavia, ho usato quanto segue per "uno" e "due": typedef char Small; class Big {char dummy [2];} per garantire nessuna ambiguità sulla dimensione della variabile dipendente dalla piattaforma.
user23167,

6
Dubito che esista sulla terra una piattaforma con la dimensione di (carattere) == dimensione di (lungo)
Nicola Bonelli,

17
Non ne sono del tutto sicuro, ma non credo sia portatile. typeof è un'estensione GCC, non funzionerà con altri compilatori.
Leon Timmermans,

56
typeof non è necessario - char [sizeof (& C :: helloworld)] funziona anche. E per evitare sizeof (long) == sizeof (char), usa un struct {char [2]} ;. Deve avere una dimensione> = 2
MSalters

57
Triviale, ma mi ci è voluto un po 'per capire: sostituirlo typeofcon decltypequando si utilizza C ++ 0x , ad esempio via -std = c ++ 0x.
hr

265

Questa domanda è vecchia, ma con C ++ 11 abbiamo ottenuto un nuovo modo per verificare l'esistenza di funzioni (o l'esistenza di un membro non di tipo, in realtà), basandosi nuovamente su SFINAE:

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

Ora su alcune spiegazioni. Per prima cosa, utilizzo l' espressione SFINAE per escludere le serialize(_imp)funzioni dalla risoluzione del sovraccarico, se la prima espressione all'interno decltypenon è valida (ovvero, la funzione non esiste).

Il void()è usato per fare il tipo di ritorno di tutte quelle funzioni void.

L' 0argomento viene utilizzato per preferire il os << objsovraccarico se entrambi sono disponibili (letterale 0è di tipo inte come tale il primo sovraccarico è una corrispondenza migliore).


Ora, probabilmente vuoi che un tratto controlli se esiste una funzione. Fortunatamente, è facile scriverlo. Si noti, però, che è necessario scrivere un tratto te stesso per ogni nome di funzione diversa si potrebbe desiderare.

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

Esempio dal vivo.

E avanti alle spiegazioni. Innanzitutto, sfinae_trueè un tipo di aiuto e sostanzialmente equivale alla scrittura decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Il vantaggio è semplicemente che è più corto.
Successivamente, struct has_stream : decltype(...)eredita da uno std::true_typeo std::false_typealla fine, a seconda che il decltypecheck-in test_streamfallisca o meno.
Infine, std::declvalti dà un "valore" di qualunque tipo passi, senza che tu abbia bisogno di sapere come puoi costruirlo. Si noti che ciò è possibile solo all'interno di un contesto non valutato, come decltype, sizeofe altri.


Si noti che decltypenon è necessariamente necessario, poiché sizeof(e tutti i contesti non valutati) hanno ottenuto quel miglioramento. È solo che decltypeoffre già un tipo e come tale è solo più pulito. Ecco una sizeofversione di uno dei sovraccarichi:

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

I parametri inte longsono ancora lì per lo stesso motivo. Il puntatore di array viene utilizzato per fornire un contesto in cui sizeofpuò essere utilizzato.


4
Il vantaggio di decltypeover sizeofè anche che non viene introdotto un temporaneo da regole appositamente predisposte per le chiamate di funzione (quindi non è necessario disporre dei diritti di accesso al distruttore del tipo restituito e non si creerà un'istanza implicita se il tipo restituito è un'istanza del modello di classe).
Johannes Schaub - litb

5
Microsoft non ha ancora implementato Expression SFINAE nel suo compilatore C ++. Immagina di poter aiutare a risparmiare tempo in alcune persone, dato che ero confuso sul perché questo non funzionasse per me. Bella soluzione però, non vedo l'ora di usarlo in Visual Studio!
Jonathan,

3
Il tuo primo link di esempio è interrotto
NathanOliver,

1
Va detto, che static_assert(has_stream<X, char>() == true, "fail X");verrà compilato e non affermato perché char è convertibile in int, quindi se quel comportamento non è desiderato e desidera che tutti i tipi di argomenti corrispondano, non so come si possa ottenere?
Gabriel,

4
Se sei perplesso quanto io ero sui due argomenti per declinare: declinare ne prende davvero solo uno; la virgola è un operatore qui. Vedi stackoverflow.com/questions/16044514/…
André

159

C ++ consente a SFINAE di essere utilizzato per questo (notare che con le funzionalità C ++ 11 questo è più semplice perché supporta SFINAE esteso su espressioni quasi arbitrarie - il seguito è stato progettato per funzionare con i compilatori C ++ 03 comuni):

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

il modello e la macro sopra tentano di creare un'istanza di un modello, assegnandogli un tipo di puntatore della funzione membro e il puntatore della funzione membro effettiva. Se i tipi non si adattano, SFINAE fa ignorare il modello. Uso come questo:

HAS_MEM_FUNC(toString, has_to_string);

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

Ma nota che non puoi semplicemente chiamare quella toStringfunzione in quel ramo se. poiché il compilatore verificherà la validità in entrambi i rami, ciò fallirebbe nei casi in cui la funzione non esiste. Un modo è usare di nuovo SFINAE (enable_if può essere ottenuto anche da boost):

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

Divertiti ad usarlo. Il vantaggio è che funziona anche per le funzioni membro sovraccaricate e anche per le funzioni membro const (ricordarsi di usare std::string(T::*)() constcome tipo di puntatore funzione membro allora!).


7
Mi piace come type_checkviene utilizzato per garantire che le firme concordino esattamente. C'è un modo per farlo in modo che corrisponda a qualsiasi metodo che potrebbe essere chiamato nel modo in cui un metodo con firma Signpotrebbe essere chiamato? (Ad esempio if Sign= std::string(T::*)(), consenti la std::string T::toString(int default = 42, ...)corrispondenza.)
j_random_hacker

5
Ho solo capito qualcosa che non era immediatamente ovvio per me, quindi nel caso in cui aiuti gli altri: chk non è e non ha bisogno di essere definito! L'operatore sizeof determina la dimensione dell'output di chk senza che chk debba mai essere chiamato.
SCFrench

3
@ deek0146: Sì, Tnon deve essere un tipo primitivo, perché la dichiarazione da puntatore a metodo di T non è soggetta a SFINAE e si risolverà in errore per qualsiasi T. non IMO la soluzione più semplice è combinare con il is_classcontrollo da Incremento.
Jan Hudec,

2
Come posso farlo funzionare se la mia toStringè una funzione basata su modelli?
Frank,

4
È (o qualcosa di equivalente) in Boost?
Dan Nissenbaum,

89

C ++ 20 - requiresespressioni

Con C ++ 20 nascono concetti e strumenti assortiti come requiresespressioni che sono un modo integrato per verificare l'esistenza di una funzione. Con loro puoi riscrivere la tua optionalToStringfunzione come segue:

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 - Toolkit di rilevamento

N4502 propone un toolkit di rilevamento per l'inclusione nella libreria standard C ++ 17 che alla fine è arrivato ai fondamenti della libreria TS v2. Molto probabilmente non entrerà mai nello standard perché è stato ripreso da requiresespressioni da allora, ma risolve ancora il problema in un modo un po 'elegante. Il toolkit introduce alcune metafunzioni, tra cui quelle std::is_detectedche possono essere utilizzate per scrivere facilmente metafunzioni di rilevamento di tipi o funzioni nella parte superiore. Ecco come potresti usarlo:

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

Si noti che l'esempio sopra non è testato. Il toolkit di rilevamento non è ancora disponibile nelle librerie standard ma la proposta contiene un'implementazione completa che puoi facilmente copiare se ne hai davvero bisogno. Funziona bene con la funzione C ++ 17 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 apparentemente si basa su questo esempio specifico e fornisce una soluzione per C ++ 14 nella sua documentazione, quindi lo citerò direttamente:

[...] Hana fornisce una is_validfunzione che può essere combinata con lambda generici C ++ 14 per ottenere un'implementazione molto più pulita della stessa cosa:

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

Questo ci lascia con un oggetto funzione has_toStringche restituisce se l'espressione data è valida sull'argomento che le passiamo. Il risultato viene restituito come un IntegralConstant, quindi la constexpr-ness non è un problema qui perché il risultato della funzione è comunque rappresentato come un tipo. Ora, oltre ad essere meno prolisso (questo è un singolo esempio!), L'intento è molto più chiaro. Altri vantaggi sono il fatto che has_toStringpuò essere passato ad algoritmi di ordine superiore e può anche essere definito nell'ambito della funzione, quindi non è necessario inquinare l'ambito dello spazio dei nomi con i dettagli di implementazione.

Boost.TTI

Un altro toolkit in qualche modo idiomatico per eseguire tale controllo - anche se meno elegante - è Boost.TTI , introdotto in Boost 1.54.0. Per il tuo esempio, dovresti usare la macro BOOST_TTI_HAS_MEMBER_FUNCTION. Ecco come potresti usarlo:

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

Quindi, è possibile utilizzare il boolper creare un controllo SFINAE.

Spiegazione

La macro BOOST_TTI_HAS_MEMBER_FUNCTIONgenera la metafunzione has_member_function_toStringche accetta il tipo selezionato come primo parametro del modello. Il secondo parametro del modello corrisponde al tipo restituito della funzione membro e i seguenti parametri corrispondono ai tipi dei parametri della funzione. Il membro valuecontiene truese la classe Tha una funzione membro std::string toString().

In alternativa, has_member_function_toStringpuò prendere un puntatore a funzione membro come parametro modello. Pertanto, è possibile sostituire has_member_function_toString<T, std::string>::valuecon has_member_function_toString<std::string T::* ()>::value.


1
più conciso di 03
ZFY

@ZFY Penso che Boost.TTI funzioni anche con C ++ 03, ma è la soluzione meno elegante del lotto.
Morwenn,

La soluzione C ++ 20 è davvero valida? Mi piacerebbe - ma è rifiutato da g ++ e msvc - accettato solo da clang.
Bernd Baumanns

su cppreference puoi leggere: Se un'espressione obbligatoria contiene tipi o espressioni non validi nei suoi requisiti e non appare nella dichiarazione di un'entità templata, allora il programma è mal formato.
Bernd Baumanns

@BerndBaumanns Davvero? Ho funzionato con il trunk GCC: godbolt.org/z/CBwZdE Forse hai ragione, ho solo verificato che funzionasse, ma non ho verificato se era legale secondo la formulazione standard.
Morwenn,

56

Sebbene questa domanda abbia due anni, oserò aggiungere la mia risposta. Spero che chiarisca la soluzione precedente, indiscutibilmente eccellente. Ho preso le risposte molto utili di Nicola Bonelli e Johannes Schaub e le ho unite in una soluzione che è, IMHO, più leggibile, chiara e non richiede l' typeofestensione:

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

L'ho controllato con gcc 4.1.2. Il merito va principalmente a Nicola Bonelli e Johannes Schaub, quindi dai loro un voto se la mia risposta ti aiuta :)


1
Mi chiedo, questo fa qualcosa che la soluzione di Konrad Rudolph qui sotto non fa?
Alastair Irvine,

3
@AlastairIrvine, questa soluzione nasconde tutta la logica al suo interno, Konrad's mette un po 'dell'onere per l'utente. Sebbene breve e molto più leggibile, la soluzione di Konrad richiede una specializzazione modello separata per ogni classe che ha toString. Se scrivi una libreria generica, che desidera lavorare con qualsiasi classe (pensa a qualcosa come boost), richiedere che l'utente definisca specializzazioni aggiuntive di alcuni modelli oscuri potrebbe essere inaccettabile. A volte è preferibile scrivere un codice molto complicato per mantenere l'interfaccia pubblica il più semplice possibile.
FireAphis,

30

Una soluzione semplice per C ++ 11:

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

Aggiornamento, 3 anni dopo: (e questo non è testato). Per verificare l'esistenza, penso che funzionerà:

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
Questo è semplice ed elegante, ma a rigor di termini non risponde alla domanda di OP: non si consente al chiamante di verificare l'esistenza di una funzione, lo si fornisce sempre . Ma bello comunque.
Adrian W,

@AdrianW, buon punto. Ho aggiornato la mia risposta. Non l'ho ancora provato
Aaron McDaid il

Nel caso in cui aiuti qualcun altro, non potrei farlo funzionare senza template<typename>prima il sovraccarico variadico: non veniva preso in considerazione per la risoluzione.
Laboratorio Cobotica,

Ancora una volta, questo è C ++ 11 non valido.
Peter,

29

Questo è il tipo di tratti che ci sono. Sfortunatamente, devono essere definiti manualmente. Nel tuo caso, immagina quanto segue:

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
dovresti preferire enum per i tratti invece delle costanti statiche: "I membri delle costanti statiche sono valori, che costringe il compilatore a creare un'istanza e allocare la definizione per il membro statico. Di conseguenza, il calcolo non è più limitato a un puro" tempo di compilazione " "effetto".
Özgür

5
"I valori di enumerazione non sono valori (cioè non hanno un indirizzo). Quindi, quando li si passa" per riferimento ", non viene utilizzata alcuna memoria statica. È quasi esattamente come se si passasse il valore calcolato come letterale Queste considerazioni ci motivano a utilizzare i valori di enumerazione "Modelli C ++: la guida completa
Özgür

22
Comptrol: no, il passaggio citato non si applica qui poiché le costanti statiche di tipo intero sono un caso speciale! Si comportano esattamente come un enum qui e sono il modo preferito. Il vecchio enum hack era necessario solo su compilatori che non seguivano lo standard C ++.
Konrad Rudolph,

3
@Roger Pate: non proprio. "Usato nel programma" qui è apparentemente sinonimo di "referenziato". La lettura prevalente di questo passaggio, e quella implementata da tutti i moderni compilatori C ++, è che puoi prendere il valore di una costante statica senza bisogno di dichiararla (la frase precedente dice questo: “… il membro può apparire in espressioni costanti integrali ...”). Devi solo definirlo se ne prendi l'indirizzo (esplicitamente tramite &T::xo implicitamente vincolandolo a un riferimento).
Konrad Rudolph,


25

Bene, questa domanda ha già un lungo elenco di risposte, ma vorrei sottolineare il commento di Morwenn: esiste una proposta per C ++ 17 che la rende davvero molto più semplice. Vedi N4502 per i dettagli, ma come esempio autonomo considerare quanto segue.

Questa parte è la parte costante, inseriscila in un'intestazione.

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

poi c'è la parte variabile, in cui si specifica ciò che si sta cercando (un tipo, un tipo di membro, una funzione, una funzione membro ecc.). Nel caso del PO:

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

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

L'esempio seguente, tratto da N4502 , mostra una sonda più elaborata:

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

Rispetto alle altre implementazioni sopra descritte, questa è abbastanza semplice: un set ridotto di strumenti ( void_tedetect ) è sufficiente, senza bisogno di macro pelose. Inoltre, è stato segnalato (vedi N4502 ) che è misurabilmente più efficiente (tempo di compilazione e consumo di memoria del compilatore) rispetto agli approcci precedenti.

Ecco un esempio dal vivo . Funziona bene con Clang, ma sfortunatamente, le versioni GCC precedenti alla 5.1 hanno seguito una diversa interpretazione dello standard C ++ 11 che ha causato il void_tmancato funzionamento come previsto. Yakk ha già fornito la soluzione: usa la seguente definizione di void_t( void_t nella lista dei parametri funziona ma non come tipo di ritorno ):

#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

È possibile estenderlo per rilevare funzioni non membro?
Plasmacel,

Si certo. Guarda attentamente gli esempi: in pratica fornisci un'espressione e verifica se è valida. Nulla richiede che questa espressione riguardi solo una chiamata di funzione membro.
Akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) è la strada del futuro ... Stavo cercando un modo pulito per rilevare cose sui tipi e N4502 è la strada andare.
Tlonuk,

11

Questa è una soluzione C ++ 11 per il problema generale se "Se avessi fatto X, si sarebbe compilato?"

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

Tratto has_to_stringtale che has_to_string<T>::valueè trueif e only if Tha un metodo .toStringche può essere invocato con 0 argomenti in questo contesto.

Quindi, userò il dispacciamento dei tag:

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

che tende ad essere più mantenibile delle espressioni SFINAE complesse.

Puoi scrivere questi tratti con una macro se ti ritrovi a farlo molto, ma sono relativamente semplici (poche righe ciascuno) quindi forse non ne vale la pena:

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

ciò che fa sopra è creare una macro MAKE_CODE_TRAIT. Si passa il nome del tratto desiderato e un codice che può testare il tipo T. Così:

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

crea la classe dei tratti sopra.

A parte questo, la tecnica di cui sopra fa parte di ciò che MS chiama "espressione SFINAE" e il loro compilatore del 2013 fallisce abbastanza duramente.

Si noti che in C ++ 1y è possibile la seguente sintassi:

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

che è un ramo condizionale della compilazione in linea che abusa di molte funzionalità C ++. Farlo probabilmente non ne vale la pena, poiché il vantaggio (del codice in linea) non vale il costo (di quasi nessuno che capisca come funziona), ma l'esistenza di tale soluzione potrebbe essere interessante.


Questo gestisce casi privati?
tower120,

@ tower120 Dovrei sperimentare: il modo in cui i modelli interagiscono con privato / pubblico / protetto è un po 'oscuro per me. Non importa dove invochi has_to_stringcomunque.
Yakk - Adam Nevraumont il

ma sai, se guardi dall'altra parte ... Possiamo raggiungere membri protetti dalla classe Derived. Forse se metti tutta questa roba INSIDE in classe, e converti da strutture a funzioni constexpr ...
tower120

Ecco, guarda questo coliru.stacked-crooked.com/a/ee94d16e7c07e093 Non riesco proprio a farlo constexpr
tower120


10

Ecco alcuni frammenti di utilizzo: * Le viscere di tutto ciò sono più in basso

Verifica la presenza di un membro xin una determinata classe. Potrebbe essere var, func, class, union o enum:

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

Verifica della funzione membro 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;

Verifica la variabile membro x:

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

Verifica la classe membro x:

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

Verifica unione membri x:

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

Verifica la presenza dei membri x:

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

Verificare la presenza di qualsiasi funzione membro xindipendentemente dalla firma:

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;

O

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;

Dettagli e core:

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

Macro (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
hai idea del perché se passiamo sig_check<func_sig, &T::func_name>al controllo gratuito delle funzioni: sig_check<func_sig, &func_name>non riesce a creare un "identificatore non dichiarato" che menziona il nome della funzione che vogliamo controllare? perché mi aspetto che SFINAE NON lo renda un errore, lo fa solo per i membri, perché non per le funzioni gratuite?
v.oddou

Presumo che avrebbe qualcosa a che fare con il fatto che una funzione libera non è una classe o una struttura. Questa tecnica per dedurre la presenza di un membro si concentra davvero sul meccanismo di ereditarietà multipla in C ++ forzando l'ambiguità tra una classe stub che esiste solo allo scopo di ospitare il membro che stai verificando rispetto alla classe che stai effettivamente controllando per il membro questa è una domanda interessante, non ci avevo pensato. Potresti cercare altre tecniche di controllo dei membri C ++ 11/14, ho visto alcune cose intelligenti nel nuovo standard.
Brett Rossier,

Grazie per la tua risposta, penso che dovrei controllare più approfonditamente le informazioni fornite sull'eredità, perché fino ad ora non ho visto alcuna correlazione tra il semplice affidamento a SFINAE per creare un'espressione che non sarebbe corretta esprimendo l'accesso a un membro in un parametro del tipo di modello e l'ereditarietà multipla. Ma credo completamente che in C ++ anche concetti distanti possano sanguinarsi a vicenda. Ora per le funzioni gratuite questa domanda è interessante: stackoverflow.com/questions/26744589 TC answer sembra usare un trucco per dichiarare un manichino per evitare l '"identificatore non dichiarato"
v.oddou

8

Ho scritto una risposta a questo in un altro thread che (a differenza delle soluzioni sopra) controlla anche le funzioni dei membri ereditate:

SFINAE per verificare le funzioni membro ereditate

Ecco alcuni esempi di quella soluzione:

Esempio 1:

Stiamo cercando un membro con la seguente firma: 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));
};

Si noti che controlla anche la costanza del metodo e funziona anche con tipi primitivi. (Intendohas_const_begin<int>::value è falso e non causa un errore di compilazione.)

Esempio 2

Ora stiamo cercando la firma: 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));
};

Si noti che MyClass non deve essere costruibile di default o per soddisfare qualsiasi concetto speciale. La tecnica funziona anche con i membri del modello.

Attendo con impazienza opinioni in merito.


7

Adesso è stato bello rompicapo - ottima domanda!

Ecco un'alternativa alla soluzione di Nicola Bonelli che non si basa sull'operatore non standard typeof.

Sfortunatamente, non funziona su GCC (MinGW) 3.4.5 o Digital Mars 8.42n, ma funziona su tutte le versioni di MSVC (incluso VC6) e su Comeau C ++.

Il blocco commenti più lungo ha i dettagli su come funziona (o dovrebbe funzionare). Come si dice, non sono sicuro di quale comportamento sia conforme agli standard - sarei lieto di commentare questo.


aggiornamento - 7 nov 2008:

Sembra che mentre questo codice sia sintatticamente corretto, il comportamento che MSVC e Comeau C ++ mostrano non segue lo standard (grazie a Leon Timmermans e litb per avermi indicato nella giusta direzione). Lo standard C ++ 03 dice quanto segue:

14.6.2 Nomi dipendenti [temp.dep]

Paragrafo 3

Nella definizione di un modello di classe o di un membro di un modello di classe, se una classe di base del modello di classe dipende da un parametro modello, l'ambito della classe di base non viene esaminato durante la ricerca di nomi non qualificati nel punto di definizione della classe modello o membro o durante un'istanza del modello o membro della classe.

Quindi, sembra che quando MSVC o Comeau considerano la toString()funzione membro Tdell'esecuzione della ricerca del nome nel sito di chiamata indoToString() quando il modello viene istanziato, ciò non è corretto (anche se in realtà è il comportamento che stavo cercando in questo caso).

Il comportamento di GCC e Digital Mars sembra essere corretto - in entrambi i casi la funzione non membro toString()è vincolata alla chiamata.

Ratti - Pensavo di aver trovato una soluzione intelligente, invece ho scoperto un paio di bug del compilatore ...


#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
No, non è conforme agli standard, anche se penso che funzionerà in GCC se si attiva l'opzione -fpermissive.
Leon Timmermans,

So che i commenti non danno molto spazio, ma potresti indicare informazioni sul perché non sono conformi agli standard? (Non sto discutendo - Sono curioso)
Michael Burr,

Mike B: lo standard dice in 3.10 p15: "Se un programma tenta di accedere al valore memorizzato di un oggetto attraverso un valore diverso da uno dei seguenti tipi, il comportamento non è definito" e tale elenco in effetti non include il caso fare.
Johannes Schaub - litb

4
non sono sicuro del motivo per cui non aggiunge un altro mio commento: la tua chiamata a toString non è qualificata. quindi chiamerà sempre la funzione libera e mai quella nella base, poiché la baseclass dipende da un parametro di tipo template.
Johannes Schaub - litb

@litb: grazie per i suggerimenti. Non credo che 3.10 si applichi qui. La chiamata a toString () all'interno di doToString () non "accede al valore memorizzato di un oggetto attraverso un valore". Ma il tuo secondo commento è corretto. Aggiornerò la risposta.
Michael Burr,

6

La soluzione C ++ standard presentata qui da litb non funzionerà come previsto se il metodo dovesse essere definito in una classe base.

Per una soluzione che gestisca questa situazione, consultare:

In russo : http://www.rsdn.ru/forum/message/2759773.1.aspx

Traduzione inglese di Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

È follemente intelligente. Tuttavia, un problema con questa soluzione è che fornisce errori del compilatore se il tipo in fase di test è uno che non può essere utilizzato come classe base (ad esempio tipi primitivi)

In Visual Studio, ho notato che se si lavora con il metodo senza argomenti, è necessario inserire una coppia aggiuntiva di ridondanti () attorno agli argomenti per dedurre () nell'espressione sizeof.


Hmm, avendo sviluppato la mia versione usando le idee dei post, ho scoperto che l'idea presenta alcuni altri svantaggi, quindi ho rimosso di nuovo il codice dalla mia risposta. Uno è che tutte le funzioni devono essere pubbliche nel tipo di destinazione. Quindi non è possibile verificare la presenza di una funzione "f" in questo: struct g { void f(); private: void f(int); };perché una delle funzioni è privata (ciò è dovuto al fatto che il codice non funziona using g::f;, se fnon è accessibile).
Johannes Schaub -

6

MSVC ha le parole chiave __if_exists e __if_not_exists ( Doc ). Insieme all'approccio typeof-SFINAE di Nicola ho potuto creare un controllo per GCC e MSVC come l'OP ha cercato.

Aggiornamento: la fonte può essere trovata qui


6

Un esempio usando SFINAE e la specializzazione parziale del modello, scrivendo un Has_foocontrollo di concetto:

#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

Ho modificato la soluzione fornita in https://stackoverflow.com/a/264088/2712152 per renderla un po 'più generale. Inoltre, poiché non utilizza nessuna delle nuove funzionalità di C ++ 11, possiamo usarlo con vecchi compilatori e dovrebbe funzionare anche con msvc. Ma i compilatori dovrebbero abilitare C99 a usarlo poiché usa macro variadiche.

La seguente macro può essere utilizzata per verificare se una determinata classe ha un particolare typedef o meno.

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

La seguente macro può essere utilizzata per verificare se una determinata classe ha una particolare funzione membro o meno con un determinato numero di argomenti.

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

Possiamo usare le 2 macro sopra per eseguire i controlli per has_typedef e has_mem_func come:

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

Puoi migliorarlo per supportare le funzioni membro con argomenti template. Cambia il template <typename T> in template <typename T, typename ... Args>, quindi puoi usare "Args ..." nella tua macro elipsis per creare una struttura di controllo con args template variabili. per esempio. Rileva il metodo "void onNext (const T &)" 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" ); };
ACyclic

4

Strano nessuno ha suggerito il seguente bel trucco che ho visto una volta proprio su questo sito:

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

Devi assicurarti che T sia una classe. Sembra che l'ambiguità nella ricerca del foo sia un fallimento di sostituzione. L'ho fatto funzionare su gcc, ma non sono sicuro che sia standard.


3

Il modello generico che può essere utilizzato per verificare se alcune "funzionalità" sono supportate dal tipo:

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

Il modello che controlla se esiste un metodo foocompatibile con la firmadouble(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*>())));

Esempi

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


C'è un modo per incorporare la has_foochiamata nel modello di is_supported. Quello che vorrei è quello di chiamare qualcosa come: std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. Il motivo di ciò, voglio definire una has_fooper ogni diversa firma della funzione che voglio controllare prima di poter verificare la funzione?
CJCombrink,

2

Che ne dici di questa soluzione?

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

Non riesce se toStringè sovraccarico, come &U::toStringè ambiguo.
Yakk - Adam Nevraumont,

@Yakk Penso che un cast possa risolvere questo problema.
user1095108

2

Ci sono molte risposte qui, ma non sono riuscito a trovare una versione che esegua l' ordinamento della risoluzione del metodo reale , senza utilizzare nessuna delle nuove funzionalità c ++ (utilizzando solo le funzionalità c ++ 98).
Nota: questa versione è stata testata e funziona con vc ++ 2013, g ++ 5.2.0 e il compilatore online.

Quindi ho ideato una versione che utilizza solo sizeof ():

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

Demo live (con controllo esteso del tipo di ritorno e soluzione alternativa a vc ++ 2010): http://cpp.sh/5b2vs

Nessuna fonte, come l'ho inventato io stesso.

Quando si esegue la demo live sul compilatore g ++, si noti che sono consentite dimensioni di array pari a 0, il che significa che static_assert utilizzato non attiverà un errore del compilatore, anche se non riesce.
Una soluzione alternativa comunemente utilizzata è quella di sostituire "typedef" nella macro con "extern".


No, ma lo sto dichiarando da solo e non usa rvalue (guarda all'inizio del mio codice). Oppure potresti semplicemente convincerti e provare la demo dal vivo in modalità c ++ 98. PS: static_assert non è neanche c ++ 98, ma ci sono soluzioni (demo live)
user3296587

D'oh! perso quello. :-)
Ian Ni-Lewis,

I tuoi assert statici non funzionano. È necessario utilizzare la dimensione dell'array -1 anziché 0 (provare a inserire static_assert(false);). Lo stavo usando in connessione con CRTP dove voglio determinare se la classe derivata ha una funzione particolare - che risulta non funzionare, eppure le tue affermazioni sono sempre passate. Ho perso dei capelli a quello.
il maiale il

Suppongo che tu stia usando g ++. Nota che gcc / g ++ ha un'estensione che consente un array di dimensioni zero ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

Potresti riscriverlo per non sovraccaricare l'operatore? ad es. scegliere un altro operatore? Inoltre, evitare l'inquinamento dello spazio dei nomi con qualcosa di diverso da has_awesome_member?
einpoklum,

1

Ecco la mia versione che gestisce tutti i possibili sovraccarichi delle funzioni membro con arità arbitraria, comprese le funzioni membro modello, possibilmente con argomenti predefiniti. Distingue 3 scenari reciprocamente esclusivi quando si effettua una chiamata di funzione membro a un tipo di classe, con determinati tipi arg: (1) valido, (2) ambiguo o (3) non praticabile. Esempio di utilizzo:

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

Ora puoi usarlo in questo modo:

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

Ecco il codice, scritto in c ++ 11, tuttavia, puoi facilmente portarlo (con piccole modifiche) a non c ++ 11 che ha estensioni di tipo (es. Gcc). È possibile sostituire la macro HAS_MEM con la propria.

#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

Puoi saltare tutta la metaprogrammazione in C ++ 14 e scrivere semplicemente usando fit::conditionaldalla libreria Fit :

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

Puoi anche creare la funzione direttamente anche dai lambda:

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

Tuttavia, se si utilizza un compilatore che non supporta lambda generici, sarà necessario scrivere oggetti funzione separati:

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
Quanto è facile scrivere questo per non dover dipendere da fituna libreria diversa dallo standard?
einpoklum,

1

Con C ++ 20 puoi scrivere quanto segue:

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

Ecco un esempio del codice di lavoro.

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>* = nullptrabiliterà la funzione che accetta un intargomento aggiuntivo che ha una priorità rispetto alla funzione che assume longquando viene chiamata con 0.

È possibile utilizzare lo stesso principio per le funzioni che vengono restituite truese la funzione è implementata.

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

Ho avuto un problema simile:

Una classe modello che può essere derivata da poche classi base, alcune con un determinato membro e altre no.

L'ho risolto in modo simile alla risposta "typeof" (di Nicola Bonelli), ma con decltype in modo da compilare e funzionare correttamente su MSVS:

#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

Un altro modo per farlo in C ++ 17 (ispirato a boost: hana).

Scrivilo una volta e usalo molte volte. Non richiede has_something<T>classi di tratti di tipo.

#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){} )

Esempio

#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
"Non abbiamo bisogno di descrizioni delle risposte" ... per favore aggiungi una descrizione informativa alla tua risposta per migliorarla. Grazie.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.