555 votes

Est-il possible d'écrire en C++ modèle pour vérifier la fonction de l'existence?

Est-il possible d'écrire en C++ modèle qui change de comportement en fonction de si un membre de la fonction est définie sur la classe?

Voici un exemple simple de ce que j'ai envie d'écrire:

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

Donc, si la classe T a "toString" défini puis il l'utilise, sinon ça ne marche pas. La partie magique que je ne sais pas comment faire, c'est la "FUNCTION_EXISTS".

357voto

Nicola Bonelli Points 4011

Oui, avec SFINAE vous pouvez vérifier si une classe donnée ne fournir une certaine méthode. Voici le code qui fonctionne:

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

J'ai juste testé avec Linux et gcc 4.1/4.3. Je ne sais pas si c'est portable sur les autres plates-formes d'exécution des compilateurs différents.

285voto

Xeo Points 69818

Cette question est ancienne, mais avec le C++11 nous avons une nouvelle façon de vérifier une des fonctions de l'existence (ou l'existence d'un non-membre de type, vraiment), en s'appuyant sur SFINAE de nouveau:

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

Exemple vivant.

Maintenant, sur certaines explications. Première chose, j'utilise l'expression SFINAE à exclure l' serialize(_imp) fonctions de résolution de surcharge, si la première expression à l'intérieur d' decltype n'est pas valide (aka, la fonction n'existe pas).

L' void() est utilisé pour faire le type de retour de l'ensemble de ces fonctions void.

L' 0 argument est utilisé pour préférer l' os << obj de surcharge si les deux sont disponibles (littérale 0 est de type int et le premier de surcharge est un meilleur match).


Maintenant, vous voulez probablement un trait de vérifier si une fonction existe. Heureusement, il est facile d'écrire que. Notez, cependant, que vous devez écrire un trait, vous-même pour chaque nom de fonction, vous voudrez peut-être.

#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 vivant.

Et sur les explications. Tout d'abord, sfinae_true est un helper de type, et il revient au même que l'écriture d' decltype(void(std::declval<T>().stream(a0)), std::true_type{}). L'avantage, c'est simplement que c'est plus court.
Ensuite, l' struct has_stream : decltype(...) hérite de soit std::true_type ou std::false_type en fin de compte, selon que l' decltype vérifier en test_stream d'échec ou pas.
Dernier, std::declval vous donne une "valeur" de ce type de col, sans que vous ayez besoin de savoir comment vous pouvez construire. Notez que ceci n'est possible à l'intérieur d'un non évaluée contexte, comme decltype, sizeof et les autres.


Notez que decltype n'est pas forcément nécessaire, sizeof (et non évaluée tous les contextes) a obtenu cette amélioration. C'est juste qu' decltype offre déjà un type et en tant que telle est juste plus propre. Voici un sizeof version de l'un des surcharges:

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

Exemple vivant.

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

164voto

C++ permet SFINAE être utilisé pour cette (remarquez qu'avec C++11 caractéristiques c'est plus simple, car il prend en charge étendue SFINAE sur presque arbitraire expressions - le ci-dessous a été conçu pour fonctionner avec le C++03 compilateurs):

#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 ci-dessus et macro tente d'instancier un modèle, en lui donnant une fonction de membre de type pointeur, et le véritable membre de pointeur de fonction. Si les types de pas, SFINAE cause le modèle pour être 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 peut pas simplement appeler qu' toString fonction que si la branche. depuis le compilateur de vérifier la validité dans les deux branches, qui serait un échec pour les cas, la fonction n'existent pas. Une façon est d'utiliser SFINAE une fois de plus (enable_if peut être obtenu à partir de boost trop):

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

Avoir du plaisir à l'utiliser. L'avantage de cela est qu'il fonctionne aussi pour la surcharge de fonctions membres, et aussi pour les fonctions membres const (rappelez-vous à l'aide de std::string(T::*)() const que la fonction de membre de type pointeur à l'époque!).

58voto

FireAphis Points 2705

Si cette question est de deux ans, je vais oser ajouter ma réponse. Nous espérons qu'il permettra de clarifier la précédente, sans contredit une excellente solution. J'ai pris la très utile réponses de Nicola Bonelli et Johannes Schaub et de les fusionner en une solution qui est, à mon humble avis, plus lisible, clair et ne nécessite pas l' typeof extension:

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

Je l'ai vérifié avec gcc 4.1.2. Le mérite en revient principalement à Nicola Bonelli et Johannes Schaub, afin de leur donner une voix si ma réponse vous aide :)

29voto

Konrad Rudolph Points 231505

C'est ce type de traits sont là. Malheureusement, ils ont défini manuellement. Dans votre cas, imaginez le suivant:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X