David Demelier

J'écris du code, de la musique et je contribue à mes projets opensource préférés.

Les User Defined Literals en C++11

Le C++11 a rajouté une fonctionnalité assez méconnue nommée user defined literals. Son nom peut paraître légèrement ambiguë parce qu’il s’agit plutôt de traiter des suffixes donnés aux types littéraux.

Avant toute chose, si vous utilisez Visual Studio vous pouvez déjà oublier cette fonctionnalité mais vous pouvez tout de même suivre le reste de l’article juste pour voir ce que vous êtes entrain de manquer (et éventuellement vous plaindre à Microsoft).

Cette fonctionnalité a donc pour but d’écrire des valeurs constantes et de faire des opérations sur ces dernières grâce à vos propres suffixes. Par exemple, on pourra écrire un code similaire :

auto color = 0x44fecb_color;
auto message = "OK"_message;

De quoi s’agit il ? Il s’agit d’implémenter des fonctions par suffixe, ici nous avons utilisé _color et _message.

Exemple avec les unités de stockage mémoire

Soit une fonction generate qui souhaite créer un fichier. Cette fonction pour être le plus simple, prend en entrée un entier déterminant le nombre d’octet à écrire. Cependant, en tant qu’utilisateur, il serait bien sympa de pouvoir donner cette taille par kilo octet ou giga octet selon l’utilisation.

À l’heure actuelle on peut déjà répondre à ce besoin en passant par une classe de base qu’on nommerait par exemple Byte puis des classes filles comme GigaByte, KiloByte, etc. C’est à peu près ce qui est fait pour les classes chrono de la bibliothèque C++. Par contre on peut pas dire que c’est le plus élégant. On va donc utiliser nos propres suffixes pour écrire ceci :

generate(500_ko);

Tout d’abord, implémentons cette version, pour rappel 1Ko == 1024 octets. Son implémentation :

constexpr unsigned operator "" _ko(unsigned long long int value)
{
    return value * 1024;
}

Avec cette fonction, à chaque fois que je vais suffixer un entier litéral par _ko, l’expression me retournera un unsigned de mon entier multiplié par 1024.

unsigned bytes = 1_ko; // équivalent à 1 * 1024

Note, comme nous avons mis la fonction constexpr, notre 1_ko est même évalué à la compilation ce qui n’altère pas du tout les performances :-).

Exemple de code complet avec son main

#include <iostream>

constexpr unsigned operator "" _ko(unsigned long long int value)
{
    return value * 1024;
}

constexpr unsigned operator "" _mo(unsigned long long int value)
{
    return value * 1048576;
}

constexpr unsigned operator "" _go(unsigned long long int value)
{
    return value * 1073741824;
}

constexpr unsigned operator "" _to(unsigned long long int value)
{
    return value * 1099511627776;
}

void generate(unsigned size)
{
    /* Create a file of specified size */
    std::cout << "Creating a file of " << size << " bytes" << std::endl;
}

int main()
{
    generate(55_ko);
    generate(1_go);
    generate(1_ko + 2_ko);

    return 0;
}

Sortie du programme :

Creating a file of 56320 bytes
Creating a file of 1073741824 bytes
Creating a file of 3072 bytes