web-dev-qa-db-fra.com

La spécialisation de std :: to_string pour les types personnalisés est-elle autorisée par la norme C ++?

En C++ 11 et versions ultérieures, est-il autorisé de se spécialiser std::to_string dans l'espace de noms std pour les types personnalisés?

namespace std {
string to_string(::MyClass const & c) { return c.toString(); }
}

Exemple d'utilisation:

int main() {
    MyClass c;
    std::cout << std::to_string(c) << std::endl;
}
36
jotik

Dans C++ 11 et versions ultérieures, est-il autorisé de spécialiser std :: to_string dans l'espace de noms std pour les types personnalisés?

Non. Tout d'abord, ce n'est pas une fonction de modèle donc vous ne pouvez pas du tout la spécialiser.

Si vous demandez l'ajout de vos propres fonctions de surcharge, la réponse reste la même.

Extrait de documentation de Extension de l'espace de noms std :

Il est un comportement indéfini d'ajouter des déclarations ou des définitions à l'espace de noms std ou à tout espace de noms imbriqué dans std, à quelques exceptions près indiquées ci-dessous

Il est autorisé d'ajouter des spécialisations de modèle pour tout modèle de bibliothèque standard à l'espace de noms std uniquement si la déclaration dépend d'un type défini par l'utilisateur et que la spécialisation satisfait à toutes les exigences du modèle d'origine, sauf lorsque ces spécialisations sont interdites.


Dans la pratique, tout fonctionnera probablement très bien, mais à proprement parler, la norme dit qu'il n'y a aucune garantie de ce qui se passera.


Edit: Je n'ai pas accès à la norme officielle, donc ce qui suit est du projet de travail gratuit (N4296) :

17.6.4.2 Utilisation de l'espace de noms

17.6.4.2.1 Espace de noms std

  1. Le comportement d'un programme C++ n'est pas défini s'il ajoute des déclarations ou des définitions à l'espace de noms std ou à un espace de noms dans l'espace de noms std, sauf indication contraire. Un programme peut ajouter une spécialisation de modèle pour tout modèle de bibliothèque standard à l'espace de noms std uniquement si la déclaration dépend d'un type défini par l'utilisateur et que la spécialisation satisfait aux exigences de bibliothèque standard pour le modèle d'origine et n'est pas explicitement interdite.181
  2. Le comportement d'un programme C++ n'est pas défini s'il déclare

    2.1 - une spécialisation explicite de toute fonction membre d'un modèle de classe de bibliothèque standard, ou

    2.2 - une spécialisation explicite de tout modèle de fonction membre d'une classe de bibliothèque standard ou d'un modèle de classe, ou

    2.3 - une spécialisation explicite ou partielle de tout modèle de classe membre d'une classe de bibliothèque standard ou d'un modèle de classe.

    Un programme ne peut instancier explicitement un modèle défini dans la bibliothèque standard que si la déclaration dépend du nom d'un type défini par l'utilisateur et que l'instanciation répond aux exigences de la bibliothèque standard pour le modèle d'origine.

  3. Une unité de traduction ne doit pas déclarer l'espace de noms std comme un espace de noms en ligne (7.3.1).
33
James Adkison

Si je ne me trompe pas, vous pouvez simplement surcharger to_string pour un type générique:

template<typename T> to_string(const T& _x) {
    return _x.toString();
}

et cela permet l'utilisation de l'ADL (recherche dépendante de l'argument) par votre programme pour choisir correctement le to_string méthode basée sur le type transmis.

12
sjrowlinson

Le meilleur moyen serait de créer votre propre fonction qui utilise std::to_string Si c'est possible ainsi que la méthode .toString() chaque fois qu'elle est disponible pour l'argument passé:

#include <type_traits>
#include <iostream>
#include <string>

struct MyClass {
   std::string toString() const { return "MyClass"; }
};

template<class T>
typename std::enable_if<std::is_same<decltype(std::declval<const T&>().toString()), std::string>::value, std::string>::type my_to_string(const T &t) {
    return t.toString();
}

template<class T>
typename std::enable_if<std::is_same<decltype(std::to_string(std::declval<T&>())), std::string>::value, std::string>::type my_to_string(const T &t) {
    return std::to_string(t);
}

int main() {
   std::cout << my_to_string(MyClass()) << std::endl; // will invoke .toString
   std::cout << my_to_string(1) << std::endl; //will invoke std::to_string
}
8
W.F.

Dans C++ 11 et versions ultérieures, est-il autorisé de spécialiser std :: to_string dans l'espace de noms std pour les types personnalisés?

Non, vous ne pouvez pas ajouter de surcharge dans l'espace de noms std pour to_string().

La bonne nouvelle est que vous n'en avez pas besoin, il existe une solution simple!

Vous pouvez fournir votre propre implémentation et laisser ADL (recherche dépendante des arguments) résoudre le problème pour vous.

Voici comment:

class A {};

std::string to_string(const A&)
{
    return "A()";
}

int main()
{
    A a;
    using std::to_string;
    std::cout << to_string(2) << ' ' << to_string(a);
}

Ici, nous avons utilisé le en utilisant la déclaration pour mettre std::to_string Dans la portée, puis nous avons utilisé l'appel non qualifié à to_string().

Maintenant, std::to_string Et ::to_string Sont visibles et le compilateur sélectionne la surcharge appropriée.

Si vous ne voulez pas écrire using std::to_string Avant d'utiliser to_string À chaque fois ou si vous craignez d'oublier d'utiliser to_string Sans l'espace de noms, vous pouvez créer une fonction d'aide

template<typename T>
std::string my_to_string(T&& t)
{
    using std::to_string;
    return to_string(std::forward<T>(t));
}

Notez que cette fonction peut être définie dans n'importe quel espace de noms et fonctionne indépendamment de l'espace de noms dans lequel les classes sont définies (elles ne doivent pas nécessairement être les mêmes).

Voir exemple .

[~ # ~] note [~ # ~] : cela fonctionne si vous êtes celui qui appelle to_string. S'il existe une bibliothèque qui appelle std::to_string Et que vous souhaitez la modifier pour vos types, vous n'avez pas de chance.

4
Makers_F