2017-10-08 2 views
4

Je tente actuellement de mettre en œuvre une fonction toString qui appelle .toString() ou std::to_string() en fonction de ce qui est disponible pour le type déduitSFINAE fallback lorsqu'une fonction n'existe pas

Jusqu'à présent, je cet extrait de travail:

#include <iostream> 
#include <string> 

template <class T> 
auto toString(const T& obj) 
     -> decltype(obj.toString(), std::string()) 
{ 
    return obj.toString(); 
} 

template <class T> 
auto toString(const T& obj) 
     -> decltype(std::to_string(obj), std::string()) 
{ 
    return std::to_string(obj); 
} 

template <class T> 
auto toString(const T& obj) 
     -> decltype(std::string(obj)) 
{ 
    return std::string(obj); 
} 

class Foo{ 
public: 
    std::string toString() const { 
    return "Hello"; 
    } 
}; 

int main() 
{ 
    Foo bar; 
    std::cout << toString(bar); 
    std::cout << toString(5); 
    std::cout << toString("Hello const char*"); 
} 

Maintenant, je veux insérer un static_assert lorsqu'aucune surcharge de ce qui précède n'est viable, car le message d'erreur GCC par défaut pour les anciennes versions de GCC n'est pas très instructif.

Comment puis-je vérifier si .toString() ou std::to_string() sont possibles pour T?

Jusqu'ici je n'ai trouvé aucun moyen de vérifier si quelque chose est pas présent, seulement l'inverse. J'espère que quelqu'un a une idée de comment résoudre ceci et merci pour votre temps.

+2

'decltype (std :: to_string (obj), std :: string()) - TIL. Mais vous devriez "annuler" la première expression. Un opérateur virgule surchargé d'un utilisateur méchant peut gâcher votre SFINAE – StoryTeller

+1

@ max66 - Pas besoin de vous répéter. Et la version OP fonctionnera avec tout ce qui est convertible en un 'std :: string' tout en fixant le type de retour de la fonction. Plutot malin. – StoryTeller

+0

@StoryTeller - vous avez raison. – max66

Répondre

5

Vous pouvez également utiliser static_assert avec un message d'erreur personnalisé:

class Dummy 
{ 
public: 
    std::string toString() const;  
private: 
    Dummy() = default; 
}; 

template <typename... Ts> 
auto toString(Ts...) 
{ 
    static_assert(std::is_same<std::tuple<Ts...>, std::tuple<Dummy>>::value, "neither std::to_str nor member toString() exists"); 
    return ""; 
} 

live example

+0

Merci, excactement ce que je voulais. Mais pourquoi celui-ci n'est-il pas choisi par rapport aux autres options? –

+2

Celui-ci peut effectivement être mal formé ... –

+0

@ W.F. de quelle manière? –

4
namespace details{ 
    template<template<class...> class, class, class...> 
    struct can_apply : std::false_type{}; 

    template<template<class...> class Z, class...Ts> 
    struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type{}; 
} 
template<template<class...> class Z, class...Ts> 
using can_apply = details::can_apply<Z, void, Ts...>; 

template<class T> 
using dot_toString_r = decltype(std::declval<T>().toString()); 

template<class T> 
using can_dot_toString = can_apply<dot_toString_r, T>; 

Je laisse can_std_to_string en tant qu'exercice.

Si vous manque void_t dans votre révision de la norme:

template<class...> struct voider { using type=void; }; 
template<class...Ts> using void_t = typename voider<Ts...>::type; 

qui fonctionne même dans les premiers compilateurs.

8

Vous devez introduire une surcharge pire que toutes les autres et la supprimer. Vous n'avez pas besoin de vérifier si aucune des fonctions de chaîne n'existe de cette façon.

Une façon populaire de le faire est d'utiliser des arguments de type C variadique:

std::string toString(...) = delete; 
+0

Merci, TIL vous pouvez supprimer une fonction non membre :). C'est la réponse la plus facile mais je préfère la réponse de @ m.s en raison du message d'erreur personnalisé qui est plus expressif dans ce cas. –