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’erreurstd::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 valeurval
et le contexte du formatcontext
.format
formate la valeurval
et l’écrit selon le format analysécontext.out()
. La valeur de retour decontext.out()
peut entrer directementstd::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é.
Un formateur de valeur unique
// 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.
Un formateur qui prend en charge un spécificateur de format
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.
Délégation
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
(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.
Héritage
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.
Et après?
Dans mon prochain article, j'implémenterai un formateur pour un type de données personnalisé avec plus d'une valeur.
(carte)
#bibliothèque #formatage #C20 #formatage #des #types #données #définis #par #lutilisateur
1707992259