2 votes

Opérateur ostream modélisé << pour plusieurs types dans un espace de noms

J'ai un espace de nom avec plusieurs structs et classes enum à l'intérieur. Pour chaque type, j'ai un toString() méthode. Voici un petit exemple :

namespace test {
    struct A {
       int i;
    };

    struct B {
       float j;
    };

    std::string toString(const A &a){
        return to_string(a.i);
    }

    std::string toString(const B &b){
        return to_string(b.j);
    }
}

Je veux fournir un modèle operator<< qui ne capture que ces types, mais pas les types en dehors de cet espace de nom :

template<class T>
std::ostream & operator<<(std::ostream &out, const T &t){
    out << toString(t);
    return out;
}

Cependant, cela me donne l'erreur de compilation suivante :

erreur : surcharge ambiguë pour 'operator<<' (les types d'opérandes sont 'std::stringstream {aka std::__cxx11:basic_stringstream<char>}' et 'const char*')

Comment puis-je écrire une surcharge d'opérateur modélisée pour cela ?

2 votes

Il existe déjà de nombreuses surcharges de operator<< disponible dans le même champ d'application que votre operator<< . 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 votre operator<< pour chaque type que vous avez mis en œuvre toString() 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.

4voto

KorelK Points 2311

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.

0 votes

Merci pour cette réponse mais pour l'instant je n'ai pas accès à c++20 dans mon projet.

0 votes

template<class T> auto operator<<(std::ostream &out, const T &t) -> decltype(out<<toString(t)) {return out << toString(t); } est fondamentalement la même que la solution C++, mais... beaucoup plus simple

0 votes

@MooingDuck Je ne savais pas que c'était l'inverse, vous avez juste élargi mes connaissances, et je l'ai ajouté à la réponse. A propos de la is_null_pointer Comme j'ai trop réfléchi à cette solution, je l'ai simplifiée. Merci de votre compréhension.

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