Je l'ai résolu en utilisant concept
& requires
de C++20 (gcc >= 10.1) :
template <typename T>
concept HaveToString = requires (T t) {
{ toString(t) };
};
template<HaveToString T>
std::ostream & operator<<(std::ostream &out, const T& t){
out << toString(t);
return out;
}
int main() {
test::A a;
std::cout << a << std::endl;
return EXIT_SUCCESS;
}
EDIT
Para C++11
:
template<typename T, typename = decltype(toString(std::declval<T>()))>
std::ostream & operator<<(std::ostream &out, const T& t){
out << toString(t);
return out;
}
Ou comme @MooingDuck mentionnés dans les commentaires :
template<typename T>
auto operator<<(std::ostream &out, const T& t) -> decltype(out<<toString(t)) {
out << toString(t);
return out;
}
Explications
Tout d'abord, un très bon article sur opérandes non évalués . Cela aidera à comprendre ce qui se passe dans les expressions : decltype(toString(std::declval<T>()))
y decltype(out<<toString(t))
qui font toutes les deux la même chose en gros-> Définir une règle selon laquelle tout appel à cette fonction, doit supporter l'appel à toString
avec la fonction T
type de paramètre.
Première approche
decltype(toString(std::declval<T>()))
Découpons cette expression complexe en sous-expressions, de l'intérieur vers l'extérieur :
decltype(toString( std::declval<T>() ))
std::declval<T>()
Dans certains très En termes simples, cela signifie que nous "supposons" avoir créé une variable de type T au moment de la compilation (si vous n'avez pas encore lu l'article, c'est le moment de le faire). La chose importante à savoir avant de continuer - nous ne l'avons pas fait, le mot important est en supposant que .
decltype( toString(std::declval<T>()) )
La magie continue jusqu'à decltype
qui vérifie le type de l'expression non évaluée qu'il contient. Ainsi, si toString
qui appelle la variable de type T existe, il renverra la valeur qui toString
les retours de fonction. Si cette fonction n'existe pas, une erreur de compilation sera levée (ou dans ce contexte, le compilateur ne déduira pas cette fonction pour le type donné).
typename = decltype(toString(std::declval<T>()))
Cette section du modèle a pour but d'activer cette fonction chaque fois que le type renvoyé par la fonction decltype
est légal.
Approche @MooingDuck
auto operator<<(std::ostream &out, const T& t) -> decltype(out<<toString(t)) { /*...*/ }
Valeur de retour : auto
C++11
: Déduit par l'expression le après l'opérateur ->
.
Après C++14
: Calculée au moment de la compilation par l'expression de retour à l'intérieur de la fonction (s'il n'y a pas d'expression de retour, la valeur de retour déduite au moment de la compilation pour void
).
-> decltype(out<<toString(t))
Définir le type de valeur de retour.
decltype(out<<toString(t))
Comme expliqué précédemment, tout ce qui entre à l'intérieur decltype
est une expression non évaluée. Le compilateur n'évaluera pas cette expression, mais il s'assurera que l'expression peut être évaluée à l'exécution (sinon une exception sera levée, ou dans ce cas, le compilateur ne déduira pas cette fonction), et il retournera le type de la valeur retournée par cette expression.
2 votes
Il existe déjà de nombreuses surcharges de
operator<<
disponible dans le même champ d'application que votreoperator<<
. Votre modèle est en conflit avec eux, puisqu'il correspond aux mêmes types que ceux pris en charge par les autres surcharges. Vous devrez simplement spécialiser votreoperator<<
pour chaque type que vous avez mis en œuvretoString()
pour.0 votes
@RemyLebeau : Qu'en est-il de l'utilisation de C++20 Concepts ? Serait-ce possible par hasard ?
0 votes
@einpoklum Je ne peux pas répondre à cette question, car je n'ai aucune expérience de l'utilisation des concepts.
1 votes
Pour l'instant, je suis en train d'écrire une surcharge normale pour chaque type. Je sais pourquoi le conflit se produit. J'aimerais savoir s'il est possible de l'éviter. Par exemple, si vous pouvez dire au compilateur que ce modèle ne doit être regardé que pour les types de cet espace de noms.
0 votes
Pour l'instant, dans le projet sur lequel je travaille, je n'ai pas accès à c++20.
0 votes
Le problème est que
test::operator<<(std::ostream &out, const T& t)
est dans latest
oui, mais il intègre également l'espace de nomsstd
en raison de l'existence d'un espace de nomsstd::ostream
ainsi que la racine::
l'espace de noms. Je ne pense pas qu'il soit possible de faire quelque chose comme "tout ce qui se trouve dans l'espace de noms". Vous aurez besoin d'un mécanisme d'identification des classes différent.0 votes
" si vous pouvez dire au compilateur que ce modèle ne doit être pris en compte que pour les types de cet espace de noms "AFAIK, il n'y a pas de moyen de faire cela spécifiquement. Cependant, jetez un coup d'œil à
std::enable_if
il peut probablement être utilisé pour activer votre modèle uniquement dans les cas suivantstoString()
est défini pour le type de paramètre du modèle.1 votes
template<class T> auto operator<<(std::ostream &out, const T &t) -> decltype(out<<toString(t)) {return out << toString(t); }
fera ce que Remy suggère0 votes
Dans quel espace de noms avez-vous placé ceci
operator<<
?0 votes
@StoryTeller-UnslanderMonica il est dans le même espace de noms de test
0 votes
@MooingDuck Pouvez-vous fournir cette réponse avec plus d'explications ?
1 votes
En rapport : Vérification de l'existence d'une fonction membre de la classe ? qui pourrait être adapté pour traiter les
toString(T)