web-dev-qa-db-fra.com

Est-il possible d'écrire un modèle pour vérifier l'existence d'une fonction?

Est-il possible d'écrire un modèle qui change de comportement en fonction de la définition d'une fonction membre sur une classe?

Voici un exemple simple de ce que je voudrais écrire:

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

Donc, si class T a défini toString(), il l'utilise; sinon, ce n'est pas le cas. La partie magique que je ne sais pas faire est la partie "FUNCTION_EXISTS".

425
andy

Oui, avec SFINAE, vous pouvez vérifier si une classe donnée fournit une certaine méthode. Voici le code de travail:

#include <iostream>

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

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    typedef long two;

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

Je viens de le tester avec Linux et gcc 4.1/4.3. Je ne sais pas s'il est portable sur d'autres plates-formes utilisant des compilateurs différents. 

295
Nicola Bonelli

Cette question est ancienne, mais avec C++ 11, nous avons un nouveau moyen de vérifier l'existence de fonctions (ou l'existence de tout membre non typé, en réalité), en nous fiant à nouveau à 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);
}

Maintenant sur quelques explications. Tout d’abord, j’utilise expression SFINAE pour exclure les fonctions serialize(_imp) de la résolution de la surcharge, si la première expression de decltype n’est pas valide (la fonction n’existe pas non plus).

La void() est utilisée pour rendre le type de retour de toutes ces fonctions void.

L'argument 0 permet de préférer la surcharge os << obj si les deux sont disponibles (le 0 littéral est de type int et la première surcharge correspond donc mieux).


Maintenant, vous voulez probablement qu'un trait vérifie si une fonction existe. Heureusement, c'est facile d'écrire ça. Notez cependant que vous devez écrire un trait vous-même pour chaque nom de fonction différent de votre choix.

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

Exemple en direct.

Et aux explications. Tout d’abord, sfinae_true est un type d’assistance, ce qui revient à écrire decltype(void(std::declval<T>().stream(a0)), std::true_type{}). L'avantage est simplement que c'est plus court.
Ensuite, la struct has_stream : decltype(...) hérite de std::true_type ou std::false_type à la fin, selon que l’archivage decltype dans test_stream échoue ou non.
Enfin, std::declval vous donne une "valeur" quel que soit le type que vous passez, sans que vous ayez besoin de savoir comment vous pouvez le construire. Notez que cela n’est possible que dans un contexte non évalué, tel que decltype, sizeof et d’autres.


Notez que decltype n'est pas nécessairement nécessaire, car sizeof (et tous les contextes non évalués) obtiennent cette amélioration. C'est juste que decltype fournit déjà un type et est donc plus propre. Voici une version sizeof de l'une des surcharges:

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

Les paramètres int et long sont toujours là pour la même raison. Le pointeur de tableau est utilisé pour fournir un contexte où sizeof peut être utilisé.

246
Xeo

C++ autorise SFINAE à être utilisé à cet effet (notez qu'avec les fonctionnalités de C++ 11, cela est plus simple, car il prend en charge l'extension SFINAE sur les expressions presque arbitraires - le code ci-dessous a été conçu pour fonctionner avec des compilateurs C++ 03 courants) :

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

le modèle et la macro ci-dessus essaient d'instancier un modèle en lui donnant un type de pointeur de fonction de membre et le pointeur de fonction de membre réel. Si les types ne correspondent pas, SFINAE fait en sorte que le modèle soit ignoré. Utilisation comme ceci:

HAS_MEM_FUNC(toString, has_to_string);

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

Mais notez que vous ne pouvez pas simplement appeler cette fonction toString dans cette branche si. puisque le compilateur vérifiera la validité dans les deux branches, cela échouerait dans les cas où la fonction n'existe pas. Une solution consiste à utiliser SFINAE à nouveau (enable_if peut également être obtenu avec 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.";
}

Amusez-vous à l'utiliser. L'avantage est qu'il fonctionne également pour les fonctions de membre surchargées, ainsi que pour les fonctions de membre const (n'oubliez pas d'utiliser std::string(T::*)() const comme type de pointeur de fonction de membre alors!). 

155

Bien que cette question ait deux ans, j'oserai ajouter ma réponse. Espérons que cela clarifiera la solution précédente, sans conteste excellente. J'ai pris les réponses très utiles de Nicola Bonelli et Johannes Schaub et les ai fusionnées dans une solution IMHO plus lisible, claire et ne nécessitant pas l'extension typeof:

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

J'ai vérifié avec gcc 4.1.2 . Le mérite revient principalement à Nicola Bonelli et Johannes Schaub, alors donnez-leur un vote positif si ma réponse vous aide :)

52
FireAphis

Boîte à outils de détection

N4502 propose une détection à intégrer dans la bibliothèque standard C++ 17, qui peut résoudre le problème de manière assez élégante. De plus, il vient d'être accepté dans les bases de la bibliothèque TS v2. Il introduit certaines métafonctions, y compris std::is_detected qui peuvent être utilisées pour écrire facilement des métafonctions de détection de type ou de fonction. Voici comment vous pouvez l'utiliser:

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

Notez que l'exemple ci-dessus n'a pas été testé. Le kit d'outils de détection n'est pas encore disponible dans les bibliothèques standard, mais la proposition contient une implémentation complète que vous pouvez facilement copier si vous en avez vraiment besoin. Il joue à Nice avec la fonctionnalité 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";
}

Boost.TTI

Une autre boîte à outils quelque peu idiomatique pour effectuer une telle vérification - même si elle est moins élégante - est Boost.TTI , introduite dans Boost 1.54.0. Pour votre exemple, vous devrez utiliser la macro BOOST_TTI_HAS_MEMBER_FUNCTION. Voici comment vous pouvez l'utiliser:

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

Ensuite, vous pouvez utiliser la variable bool pour créer un contrôle SFINAE.

Explication

La macro BOOST_TTI_HAS_MEMBER_FUNCTION génère la métafonction has_member_function_toString qui prend le type vérifié en tant que premier paramètre de modèle. Le second paramètre de modèle correspond au type de retour de la fonction membre et les paramètres suivants correspondent aux types de paramètres de la fonction. Le membre value contient true si la classe T a une fonction membre std::string toString().

Sinon, has_member_function_toString peut prendre un pointeur de fonction membre comme paramètre de modèle. Par conséquent, il est possible de remplacer has_member_function_toString<T, std::string>::value par has_member_function_toString<std::string T::* ()>::value.

45
Morwenn

C'est pour ça que les traits de caractères sont là. Malheureusement, ils doivent être définis manuellement. Dans votre cas, imaginez ce qui suit:

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;
}
28
Konrad Rudolph

Une solution simple pour C++ 11:

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

Mise à jour, 3 ans plus tard: (et cela n’a pas été testé). Pour tester l'existence, je pense que cela fonctionnera:

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";
}
28
Aaron McDaid

Eh bien, cette question a déjà une longue liste de réponses, mais je voudrais insister sur le commentaire de Morwenn: il existe une proposition pour C++ 17 qui la simplifie vraiment beaucoup. Voir N4502 pour plus de détails, mais considérons ce qui suit comme exemple autonome.

Cette partie est la partie constante, mettez-la dans un en-tête.

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

ensuite, il y a la partie variable, où vous spécifiez ce que vous recherchez (un type, un type de membre, une fonction, une fonction de membre, etc.). Dans le cas du PO:

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

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

L'exemple suivant, tiré de N4502 , montre une sonde plus élaborée:

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

Comparée aux autres implémentations décrites ci-dessus, celle-ci est assez simple: un ensemble réduit d’outils (void_t et detect) suffit, aucun besoin de macros poilues. En outre, il a été signalé (voir N4502 ) que cette méthode est nettement plus efficace (utilisation de la compilation et de la mémoire du compilateur) que les approches précédentes.

Voici un exemple live . Cela fonctionne très bien avec Clang, mais malheureusement, les versions de GCC antérieures à la version 5.1 suivaient une interprétation différente de la norme C++ 11, ce qui faisait que void_t ne fonctionnait pas comme prévu. Yakk a déjà fourni la solution de contournement: utilisez la définition suivante de void_t ( void_t dans la liste de paramètres fonctionne, mais pas comme type de retour ):

#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
20
akim

Ceci est une solution C++ 11 au problème général si "Si je faisais X, le compilerait-il?"

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

Traiter has_to_string tel que has_to_string<T>::value est true si et seulement si T a une méthode .toString qui peut être appelée avec 0 argument dans ce contexte.

Ensuite, je voudrais utiliser la répartition des balises:

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

qui a tendance à être plus facile à gérer que les expressions SFINAE complexes.

Vous pouvez écrire ces traits avec une macro si vous le faites beaucoup, mais ils sont relativement simples (quelques lignes chacun), alors peut-être que cela n'en vaut pas la peine:

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

ce qui précède crée une macro MAKE_CODE_TRAIT. Vous lui transmettez le nom du trait souhaité et un code permettant de tester le type T. Ainsi:

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

crée la classe de traits ci-dessus.

En passant, la technique ci-dessus fait partie de ce que MS appelle "expression SFINAE", et son compilateur 2013 échoue assez fort.

Notez qu'en C++ 1y, la syntaxe suivante est possible:

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

qui est une branche conditionnelle de compilation en ligne qui abuse de nombreuses fonctionnalités C++. Cela ne vaut probablement pas la peine, car l'avantage (du code en ligne) ne vaut pas le coût (presque personne ne comprend comment cela fonctionne), mais l'existence de la solution ci-dessus peut présenter un intérêt.

Voici quelques extraits d’utilisation: * Les entrailles de tout cela sont plus loin.

Recherchez le membre x dans une classe donnée. Pourrait être var, func, classe, union ou enum:

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

Vérifier la fonction membre 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;

Vérifier la variable membre x:

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

Vérifier la classe de membres x:

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

Chèque du syndicat membre x:

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

Vérifier le nom du membre x:

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

Recherche d'une fonction membre x indépendamment de la signature:

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;

OR

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;

Détails et noyau:

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

Macros (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)
8
Brett Rossier

J'ai écrit une réponse à cela dans un autre thread qui (contrairement aux solutions ci-dessus) vérifie également les fonctions des membres hérités:

SFINAE pour vérifier les fonctions membres héritées

Voici quelques exemples de cette solution:

Exemple 1:

Nous recherchons un membre avec la signature suivante: 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));
};

Veuillez noter qu'il vérifie même la constance de la méthode et qu'il fonctionne également avec des types primitifs. (Je veux dire que has_const_begin<int>::value est faux et ne provoque pas d'erreur de compilation.) 

Exemple 2

Nous cherchons maintenant la signature: 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));
};

Veuillez noter que MyClass ne doit pas nécessairement être constructible par défaut ou satisfaire tout concept particulier. La technique fonctionne également avec les membres du modèle.

J'attends avec impatience les opinions à ce sujet.

6
kispaljr

La solution C++ standard présentée ici par litb ne fonctionnera pas correctement si la méthode est définie dans une classe de base. 

Pour une solution qui gère cette situation, reportez-vous à:

En russe: http://www.rsdn.ru/forum/message/2759773.1.aspx

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

C'est incroyablement intelligent. Cependant, un problème avec cette solution est qu’il donne des erreurs de compilation si le type testé est un type qui ne peut pas être utilisé comme classe de base (par exemple, des types primitifs).

Dans Visual Studio, j'ai remarqué que si vous travaillez avec une méthode sans arguments, vous devez insérer une paire supplémentaire de redondant () autour des arguments pour déduire () dans l'expression sizeof.

6
Roshan

C’était un Nice petit casse-tête - excellente question!

Voici une alternative à la solution de Nicola Bonelli qui ne repose pas sur l'opérateur non standard typeof.

Malheureusement, cela ne fonctionne pas sur GCC (MinGW) 3.4.5 ou Digital Mars 8.42n, mais cela fonctionne sur toutes les versions de MSVC (y compris VC6) et sur Comeau C++.

Le bloc de commentaires plus long contient des détails sur son fonctionnement (ou est censé fonctionner). Comme il est dit, je ne sais pas quel comportement est conforme aux normes - je serais heureux de recevoir des commentaires à ce sujet.


mise à jour - 7 nov 2008:

Il semble que bien que ce code soit syntaxiquement correct, le comportement que MSVC et Comeau C++ affichent ne respecte pas la norme (grâce à Leon Timmermans et litb pour m'avoir orienté dans la bonne direction). La norme C++ 03 dit ce qui suit:

14.6.2 Noms dépendants [temp.dep]

Paragraphe 3

Dans la définition d'un modèle de classe ou un membre d'un modèle de classe, si un classe de base du modèle de classe dépend d'un modèle-paramètre, le la portée de la classe de base n'est pas examinée lors de la recherche de nom non qualifiée, soit au point de définition du modèle de classe ou membre ou pendant un instanciation du modèle de classe ou membre.

Ainsi, il semble que lorsque MSVC ou Comeau considèrent la fonction membre toString() de T effectuant la recherche de nom sur le site d'appel dans doToString() lorsque le modèle est instancié, c'est incorrect (même si c'est le comportement que je recherchais dans ce cas) .

Le comportement de GCC et de Digital Mars semble être correct: dans les deux cas, la fonction toString() non membre est liée à l'appel.

Rats - Je pensais avoir trouvé une solution intelligente, mais j'ai découvert quelques bogues du compilateur ...


#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;
}
6
Michael Burr

MSVC a les mots-clés __if_exists et __if_not_exists ( Doc ). Avec l’approche typeof-SFINAE de Nicola, je pouvais créer un chèque pour GCC et MSVC tel que l’OP recherché.

Mise à jour: La source peut être trouvée Ici

5
nob

J'ai modifié la solution fournie dans https://stackoverflow.com/a/264088/2712152 pour la rendre un peu plus générale. De plus, comme il n'utilise aucune des nouvelles fonctionnalités de C++ 11, nous pouvons l'utiliser avec d'anciens compilateurs et nous devrions également travailler avec msvc. Mais les compilateurs devraient permettre à C99 de l'utiliser car il utilise des macros variadiques.

La macro suivante peut être utilisée pour vérifier si une classe particulière a un typedef particulier ou non.

/** 
 * @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 macro suivante peut être utilisée pour vérifier si une classe particulière a une fonction membre particulière ou non avec un nombre donné d'arguments.

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

Nous pouvons utiliser les 2 macros ci-dessus pour effectuer les vérifications de has_typedef et has_mem_func en tant que:

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

Le modèle générique pouvant être utilisé pour vérifier si une "fonctionnalité" est prise en charge par le type:

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

Le modèle qui vérifie s'il existe une méthode foo qui est compatible avec la signature double(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*>())));

Exemples

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

3
anton_rh

Étrange personne n’a suggéré le tour suivant que j’ai vu une fois sur ce site:

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

Vous devez vous assurer que T est une classe. Il semble que l'ambiguïté dans la recherche de foo soit un échec de substitution. Je l'ai fait fonctionner sur gcc, je ne suis pas sûr que ce soit standard.

3
Alexandre C.

Un exemple utilisant SFINAE et la spécialisation partielle de modèle, en écrivant une vérification de concept Has_foo

#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");
3
Paul Belanger

Il y a beaucoup de réponses ici, mais j'ai échoué, pour trouver une version, qui effectue un ordre de résolution de la méthode real, tout en n'utilisant aucune des nouvelles fonctionnalités de c ++ (uniquement avec les fonctionnalités de c ++ 98).
Remarque: Cette version est testée et fonctionne avec vc ++ 2013, g ++ 5.2.0 et le compilateur onlline.

Donc, je suis arrivé avec une version, qui utilise seulement 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,"");

Démonstration en direct (avec vérification étendue du type de retour et solution de contournement vc ++ 2010): http://cpp.sh/5b2vs

Aucune source, comme je l'ai moi-même trouvé.

Lorsque vous exécutez la démonstration en direct sur le compilateur g ++, veuillez noter que les tailles de tableau de 0 sont autorisées, ce qui signifie que le static_assert utilisé ne déclenchera pas d'erreur du compilateur, même en cas d'échec.
Une solution couramment utilisée consiste à remplacer le "typedef" dans la macro par "extern".

2
user3296587

Voici ma version qui gère toutes les surcharges de fonction de membre possibles avec une arité arbitraire, y compris les fonctions de membre de modèle, éventuellement avec des arguments par défaut. Il distingue 3 scénarios mutuellement exclusifs lors de l'appel d'un fonction membre à un type de classe donné, avec des types arg donnés: (1) valide, ou (2) ambigu, ou (3) non viable. Exemple d'utilisation:

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

Maintenant, vous pouvez l'utiliser comme ceci:

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

Voici le code, écrit en c ++ 11, cependant, vous pouvez facilement le porter (avec des ajustements mineurs) vers un non-c ++ 11 qui possède des extensions typeof (par exemple, gcc). Vous pouvez remplacer la macro HAS_MEM par la vôtre.

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

Que diriez-vous de cette solution?

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

Vous pouvez ignorer toutes les métaprogrammations en C++ 14 et les écrire simplement avec fit::conditional depuis la bibliothèque 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);
}

Vous pouvez également créer la fonction directement à partir des lambdas:

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

Toutefois, si vous utilisez un compilateur qui ne prend pas en charge les lambdas génériques, vous devrez écrire des objets fonction distincts:

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
Paul Fultz II

J'avais un problème similaire:

Une classe de modèle pouvant être dérivée de quelques classes de base, certaines ayant un certain membre et d'autres non.

Je l'ai résolu de la même manière que la réponse "typeof" (Nicola Bonelli), mais avec decltype pour qu'il soit compilé et fonctionne correctement sur 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
Yigal Eilam
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!";
}
0
Abhishek

Voici un exemple de code de travail.

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>* = nullptr activera la fonction qui prend un argument supplémentaire int qui a la priorité sur la fonction qui prend long lorsqu'elle est appelée avec 0.

Vous pouvez utiliser le même principe pour les fonctions, qui renvoie true si la fonction est implémentée.

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
tereshkd