138 votes

Quelles nouvelles capacités définies par l'utilisateur littéraux ajouter à C++?

C++11 introduit définis par l'utilisateur littéraux qui va permettre l'introduction de nouveaux littérale de la syntaxe basée sur les littéraux (int, hex, string, float), de sorte que n'importe quel type sera en mesure d'avoir un littéral présentation.

Exemples:

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

À première vue, cela semble très cool, mais je me demande comment applicables, il est vraiment, quand j'ai essayé de penser à avoir les suffixes _AD et _BC dates de création, j'ai trouvé que c'est problématique en raison de l'exploitant de l'ordre. 1974/01/06_AD devrait d'abord évaluer 1974/01 (brut ints), et plus tard, l' 06_AD (pour ne rien dire du mois d'août et de septembre avoir à être écrit sans l' 0 pour octal raisons). Cela peut être contourné en ayant la syntaxe être 1974-1/6_AD , de sorte que l'opérateur ordre d'évaluation des œuvres, mais il est maladroit.

Donc, ce que ma question se résume à ceci, vous sentez-vous de cette fonction permettra de justifier soi-même? Quelles sont les autres littéraux vous souhaitez définir qui fera de votre C++ code plus lisible?


Mise à jour de la syntaxe pour adapter le projet final de juin 2011

190voto

paercebal Points 38526

À première vue, cela semble être simple sucre syntaxique.

Mais quand on regarde plus profondément, nous voyons c'est plus que du sucre syntaxique, comme il étend le C++ de l'utilisateur des options pour créer des types définis par l'utilisateur qui se comportent exactement comme distincts des types intégrés. En cela, ce petit "bonus" est très intéressant de C++11 pour plus de C++.

Avons-nous vraiment besoin de ça en C++?

Je vois quelques utilisations dans le code que j'ai écrit dans les dernières années, mais juste parce que je n'ai pas l'utiliser en C++ ne signifie pas qu'il n'est pas intéressant pour un autre développeur C++ .

Nous avions utilisé en C++ (et en C, je suppose), le compilateur défini les littéraux de type de nombres entiers que de courte ou de longue nombres entiers, les nombres réels comme float ou double (ou même à long double), et les chaînes de caractères normale ou à l'échelle de la station.

En C++, nous avons eu la possibilité de créer nos propres types (c'est à dire les classes), avec potentiellement pas de frais généraux (l'in-lining, etc.). Nous avons eu la possibilité d'ajouter des opérateurs à leurs types, de les faire se comporter comme similaires, des types intégrés, ce qui permet aux développeurs C++ utilisent des matrices et des nombres complexes comme naturellement, comme si ceux-ci ont été ajoutés à la langue elle-même. On peut même ajouter des opérateurs de transtypage (ce qui est généralement une mauvaise idée, mais parfois, c'est simplement la bonne solution).

Nous avons encore manqué une seule chose à l'utilisateur les types se comportent comme des types intégrés: défini par l'utilisateur des littéraux.

Donc, je suppose que c'est une évolution naturelle de la langue, mais d'être aussi complet que possible: "Si vous voulez créer un type et que vous voulez qu'il se comporte comme beaucoup comme l'un des types intégrés, voici les outils..."

Je suppose que c'est très similaire .NET décision de faire de chaque primitive d'une struct, y compris les booléens, les entiers, etc., et de disposer de toutes les structures de dériver de l'Objet. Cette seule décision met .NET bien au-delà de Java atteindre lorsque l'on travaille avec des primitives, n'importe comment beaucoup boxing/unboxing hacks Java va ajouter à son cahier des charges.

Avez-VOUS vraiment besoin de ça en C++?

Cette question est pour VOUS répondre. Pas De Bjarne Stroustrup. Pas De Herb Sutter. Pas n'importe lequel des membres de la norme C++ comité. C'est pourquoi vous avez le choix en C++, et ils ne restreignent pas une notation utile de types intégrés seul.

Si vous avez besoin, alors c'est un ajout bienvenu. Si vous ne le faites pas, eh bien... Ne l'utilisez pas. Il vous en coûtera rien.

Bienvenue au C++, le langage où les fonctionnalités sont optionnelles.

Ballonnement??? Montrez-moi vos complexes!!!

Il y a une différence entre le lourd et complexe (pun intended).

Comme indiqué par Niels à Ce que de nouvelles capacités définies par l'utilisateur littéraux ajouter à C++?, etre capable d'écrire un nombre complexe est l'un des deux fonctionnalités ajoutées "récemment" pour C et C++:

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

Maintenant, les deux C99 "double" complexe de type et C++ "std::complex" type sont en mesure d'être multiplié, additionnées, soustraites, etc., à l'aide de la surcharge d'opérateur.

Mais en C99, ils ont juste ajouté un autre type de type intégré, et intégré dans la surcharge d'opérateur d'appui. Et ils ont ajouté un autre construit-dans le sens littéral de la fonctionnalité.

En C++, ils ont juste utilisé les fonctionnalités existantes de la langue, vu que la lettre était une évolution naturelle de la langue, et donc de l'ajouter.

En C, si vous avez besoin de la notation d'amélioration pour un autre type, vous êtes hors de la chance jusqu'à ce que votre lobbying pour ajouter votre quantique des fonctions d'onde (ou de points 3D, ou ce type de base que vous utilisez dans votre champ de travail) à la norme C comme un type qui réussit.

En C++11, vous ne pouvez le faire vous-même:

Point p = 25_x + 13_y + 3_z ; // 3D point

Est-il gonflé? Non, le besoin est là, comme l'a montré comment C et C++ complexes ont besoin d'un moyen de représenter leur littérale valeurs complexes.

Est-il mal conçu? Non, il est conçu comme tous les autres, C++, avec une extensibilité à l'esprit.

Est-il pour la notation seulement? Non, car il peut même ajouter un type de sécurité de votre code.

Par exemple, imaginons un CSS code orienté:

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

Il est alors très facile à mettre en œuvre un typage fort à la cession de valeurs.

Est-il dangereux?

Bonne question. Ces fonctions peuvent être des espaces? Si oui, alors le Jackpot!

De toute façon, comme tout le reste, vous pouvez le tuer vous-même si un outil est utilisé de manière inappropriée. C est puissant, et vous pouvez tirer votre tête si vous abusez de la C pistolet. C++ le C pistolet, mais aussi le scalpel, le taser, et tout autre outil que vous trouverez dans la boîte à outils. Vous pouvez abuser du scalpel et de purge vous-même à la mort. Ou vous pouvez créer très élégant et robuste code.

Alors, comme chaque C++ fonction, avez-vous vraiment besoin? C'est la question que vous devez répondre avant de l'utiliser en C++. Si vous ne le faites pas, il vous en coûtera rien. Mais si vous ne vraiment besoin d'elle, au moins, la langue ne vous laissera pas tomber.

La date exemple?

Votre erreur, il me semble, c'est que vous mélangez les opérateurs:

1974/01/06AD
    ^  ^  ^

Cela ne peut pas être évité, parce que / étant un opérateur, le compilateur doit l'interpréter. Et, autant que je sache, c'est une bonne chose.

Pour trouver une solution à votre problème, je voudrais écrire la traduction littérale d'une autre façon. Par exemple:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

Personnellement, je choisirais l'entier et la norme ISO dates, mais cela dépend de VOS besoins. Ce qui est le point de l'ensemble de l'permettant à l'utilisateur de définir ses propres littérale des noms.

70voto

emsr Points 4616

Voici un cas où il y a un avantage de l'utilisation définies par l'utilisateur littéraux au lieu d'un appel du constructeur:

#include <bitset>
#include <iostream>

template<char... Bits>
  struct checkbits
  {
    static const bool valid = false;
  };

template<char High, char... Bits>
  struct checkbits<High, Bits...>
  {
    static const bool valid = (High == '0' || High == '1')
                   && checkbits<Bits...>::valid;
  };

template<char High>
  struct checkbits<High>
  {
    static const bool valid = (High == '0' || High == '1');
  };

template<char... Bits>
  inline constexpr std::bitset<sizeof...(Bits)>
  operator"" _bits() noexcept
  {
    static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
    return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
  }

int
main()
{
  auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits;
  std::cout << bits << std::endl;
  std::cout << "size = " << bits.size() << std::endl;
  std::cout << "count = " << bits.count() << std::endl;
  std::cout << "value = " << bits.to_ullong() << std::endl;

  //  This triggers the static_assert at compile time.
  auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits;

  //  This throws at run time.
  std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits");
}

L'avantage est qu'une exception d'exécution est converti à une erreur de compilation. Vous ne pouvez pas ajouter de la statique affirmer à la bitset ctor prend une chaîne de caractère (au moins pas sans le modèle de chaîne d'arguments).

36voto

Nils Pipenbrinck Points 41006

C'est très agréable pour les codes mathématiques. Hors de mon esprit, je peux voir l'utilisation pour les opérateurs suivants:

deg pour les degrés. Qui fait de l'écriture absolue des angles beaucoup plus intuitive.

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

Il peut également être utilisé pour divers point fixe des représentations (qui sont encore en usage dans le domaine de la DSP et les graphiques).

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

Ils ressemblent à nice exemples comment l'utiliser. Ils aident à faire de constantes dans le code plus lisible. Il est un autre outil afin de rendre le code illisible, mais nous avons déjà tellement outils de l'abus que l'on n'a pas fait beaucoup de mal.

17voto

coppro Points 10692

UDLs sont des espaces (et peut être importés à l'aide de déclarations et directives, mais vous ne pouvez pas explicitement l'espace de noms d'un littéral comme 3.14std::i), ce qui signifie qu'il y a (je l'espère) ne sera pas une tonne d'affrontements.

Le fait qu'ils peuvent réellement être basé sur un modèle (et constexpr avais) signifie que vous pouvez faire un peu de substance assez puissante avec des UDLs. Bigint auteurs seront vraiment heureux, car ils peuvent enfin avoir arbitrairement grandes constantes, calculé au moment de la compilation (via constexpr ou modèles).

Je suis juste triste que nous ne verrons pas un couple utile littéraux dans la norme (apparemment), comme s pour std::string et i pour l'imaginaire de l'unité.

La quantité de codage qui sera sauvé par l'UDLs est en fait pas très élevé, mais la lisibilité sera considérablement accrue et plus et plus de calculs peut être déplacé au moment de la compilation pour une exécution plus rapide.

12voto

Diego Sevilla Points 17274

Permettez-moi d'ajouter un peu de contexte. Pour notre travail, défini par l'utilisateur des littéraux est d'autant plus nécessaire. Nous travaillons sur la MDE (Model Driven Engineering). Nous voulons définir les modèles et les méta-modèles en C++. Nous avons effectivement mis en place une cartographie de l'Ecore de C++ (EMF4CPP).

Le problème vient quand être en mesure de définir les éléments du modèle de classes en C++. Nous sommes à l'approche de transformation de la méta-modèle (Ecore) pour les modèles avec des arguments. Les Arguments du modèle sont les caractéristiques structurelles de types et de classes. Par exemple, une classe avec deux int attributs serait quelque chose comme:

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

Toutefois, il s'avère que chaque élément dans un modèle ou d'un métamodèle, en général un nom. Nous tenons à écrire:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

MAIS, C++, ni C++0x ne permettent pas cela, car les chaînes sont interdites en tant qu'arguments à des modèles. Vous pouvez écrire le nom de char par char, mais c'est peut l'admettre comme un gâchis. Avec une bonne définis par l'utilisateur littéraux, on pourrait écrire quelque chose de similaire. Dire que nous utilisons des "_n" pour identifier le modèle des noms d'éléments (je n'utilise pas la syntaxe exacte, juste faire une idée):

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

Enfin, pour avoir ces définitions que les modèles nous aide beaucoup à la conception d'algorithmes pour parcourir les éléments d'un modèle, les transformations de modèles, etc. qui sont vraiment efficace, parce que le type de l'information, de l'identification, transformations, etc. sont déterminés par le compilateur au moment de la compilation.

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