Nouvelles Du Monde

La bibliothèque de formatage en C++20 : formatage des types de données définis par l’utilisateur

La bibliothèque de formatage en C++20 : formatage des types de données définis par l’utilisateur

2024-02-12 14:03:00




Rainer Grimm travaille depuis de nombreuses années en tant qu’architecte logiciel, responsable d’équipe et de formation. Il aime écrire des articles sur les langages de programmation C++, Python et Haskell, mais aime également intervenir fréquemment lors de conférences spécialisées. Sur son blog Modern C++, il parle intensément de sa passion C++.

Dans mes articles précédents, j’ai discuté des types de base et std::string formaté :

Publicité

Je vais maintenant me concentrer sur le formatage des types personnalisés.

std::formatter permet de formater des types de données personnalisés. Pour ce faire, vous devez suivre le cours std::formatter spécialisez-vous pour le type personnalisé. En particulier, les fonctions membres doivent parse et format être implémenté.

  • parse: Cette fonction analyse la chaîne de format et en renvoie une en cas d’erreur std::format_error hors de.

La fonction parse devrait constexpr pour activer l’analyse au moment de la compilation. Il accepte un contexte d’analyse (std::format_parse_context) et doit renvoyer le dernier caractère du spécificateur de format (l’accolade fermante }). Si vous n’utilisez pas le spécificateur de format, il s’agit également du premier caractère du spécificateur de format.

Les lignes suivantes montrent quelques exemples du premier caractère du spécificateur de format :

"{}"          // context.begin() points to }
"{:}"         // context.begin() points to }
"{0:d}"       // context.begin() points to d} 
"{:5.3f}"     // context.begin() points to: 5.3f}
"{:x}"        // context.begin() points to x} 
"{0} {0}"     // context.begin() points to: "} {0}"

context.begin() pointe vers le premier caractère du spécificateur de format et context.end() jusqu’au dernier caractère de la chaîne de format entière. Si vous ne spécifiez pas de spécificateur de format, vous devez utiliser tout ce qui se trouve entre context.begin() et context.end() analyser et la position de la finale } rendre.

  • format: Cette fonction doit être constante. Elle obtient la valeur val et le contexte du format context. formatformate la valeur val et l’écrit selon le format analysé context.out(). La valeur de retour de context.out() peut entrer directement std::format_to être saisi. std::format_to doit renvoyer la nouvelle position pour une sortie ultérieure. Il renvoie un itérateur représentant la fin de la sortie.

Voilà pour la théorie. Maintenant, j’aimerais utiliser des exemples pour montrer comment cela peut être appliqué.

// formatSingleValue.cpp

#include 
#include 

class SingleValue {                                          // (1)
 public: 
   SingleValue() = default;
   explicit SingleValue(int s): singleValue{s} {}
   int getValue() const {                                    // (2)
     return singleValue;
   }
 private:
   int singleValue{};
};

template<>                                                   // (3)
struct std::formatter {
  constexpr auto parse(std::format_parse_context& context) { // (4)
    return context.begin();
  }
  auto format(const SingleValue& sVal, std::format_context& context) const {  // (5)
    return std::format_to(context.out(), "{}", sVal.getValue());
  }
};

int main() {

  std::cout << 'n'; 

  SingleValue sVal0;
  SingleValue sVal2020{2020};
  SingleValue sVal2023{2023};

  std::cout << std::format("Single Value: {} {} {}n", sVal0, sVal2020, sVal2023);
  std::cout << std::format("Single Value: {1} {1} {1}n", sVal0, sVal2020, sVal2023);
  std::cout << std::format("Single Value: {2} {1} {0}n", sVal0, sVal2020, sVal2023);

  std::cout << 'n';

}

SingleValue (Ligne 1) est une classe qui n'a qu'une seule valeur. La fonction membre getValue (Ligne 2) renvoie cette valeur. je me spécialise std::formatter (ligne 3) pour SingleValue. Cette spécialisation possède les fonctions parse (ligne 4) et format (ligne 5). parse renvoie la fin de la spécification de format. La fin de la spécification du format est la dernière }. format formate la valeur, et context.out crée un objet qu'un std::format_to est remis. format renvoie la nouvelle position pour une sortie ultérieure.

L'exécution de ce programme donne le résultat attendu :



Cependant, ce formateur présente un sérieux inconvénient : il ne prend en charge aucune spécification de format. J'améliorerai cela dans l'exemple suivant.

L'implémentation d'un formateur pour un type de données personnalisé est assez simple si vous utilisez un formateur standard pour le formateur. Il existe deux manières d'utiliser un formateur standard : la délégation et l'héritage.

Le formateur suivant délègue sa tâche à un formateur standard.

// formatSingleValueDelegation.cpp

#include 
#include 

class SingleValue {
 public: 
    SingleValue() = default;
    explicit SingleValue(int s): singleValue{s} {}
    int getValue() const {
        return singleValue;
    }
 private:
    int singleValue{};
};

template<>                                                         // (1)
struct std::formatter {

    std::formatter formatter;                                 // (2)

    constexpr auto parse(std::format_parse_context& context) {
        return formatter.parse(context);                           // (3)
    }

    auto format(const SingleValue& singleValue, std::format_context& context) const {
        return formatter.format(singleValue.getValue(), context);  // (4)
    }

}; 

int main() {

    std::cout << 'n'; 

    SingleValue singleValue0;
    SingleValue singleValue2020{2020};
    SingleValue singleValue2023{2023};

    std::cout << std::format("{:*<10}", singleValue0) << 'n';
    std::cout << std::format("{:*^10}", singleValue2020) << 'n';
    std::cout << std::format("{:*>10}", singleValue2023) << 'n';

    std::cout << 'n';

}

std::formatter (Ligne 1) a un formateur par défaut pour int: std::formatter formatter (Ligne 2). Je délègue le travail d'analyse au formateur (ligne 3). En conséquence, le travail de formatage est délégué au formateur (ligne 4).

La sortie du programme montre que le formateur prend en charge le remplissage et l'alignement.



Grâce à l'héritage, l'implémentation du formateur pour le type de données défini par l'utilisateur est réussie SingleValue très facile.

// formatSingleValueInheritance.cpp

#include 
#include 

class SingleValue {
 public: 
    SingleValue() = default;
    explicit SingleValue(int s): singleValue{s} {}
    int getValue() const {
        return singleValue;
    }
 private:
    int singleValue{};
};

template<>
struct std::formatter : std::formatter {             // (1)
  auto format(const SingleValue& singleValue, std::format_context& context) const {
    return std::formatter::format(singleValue.getValue(), context);
  }
};

int main() {

    std::cout << 'n'; 

    SingleValue singleValue0;
    SingleValue singleValue2020{2020};
    SingleValue singleValue2023{2023};

    std::cout << std::format("{:*<10}", singleValue0) << 'n';
    std::cout << std::format("{:*^10}", singleValue2020) << 'n';
    std::cout << std::format("{:*>10}", singleValue2023) << 'n';

    std::cout << 'n';

}

je mène std::formatter von std::formatter de (ligne 1). Seule la fonction de formatage doit être implémentée. La sortie de ce programme est identique à la sortie du programme précédent formatSingleValueDelegation.cpp.

Déléguer ou hériter d’un formateur standard est un moyen simple d’implémenter un formateur personnalisé. Cette stratégie ne fonctionne que pour les types de données personnalisés ayant une valeur.

Dans mon prochain article, j'implémenterai un formateur pour un type de données personnalisé avec plus d'une valeur.


(carte)

Vers la page d'accueil



#bibliothèque #formatage #C20 #formatage #des #types #données #définis #par #lutilisateur
1707992259

Facebook
Twitter
LinkedIn
Pinterest

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ADVERTISEMENT