Caractéristiques de C++ – quelques liens pertinents

Cette section est touffue du fait (a) qu'on parle d'un langage très riche et très puissant et (b) parce que le sujet m'intéresse particulièrement, il faut bien l'avouer. La liste qui suit n'est pas exhaustive; c'est simplement ce que j'ai eu le temps d'assembler...

CaractéristiqueDepuis...

Argument-Dependent Lookup (ADL)

C++ 98

Algorithmes

C++ 98

Alias avec using

C++ 11

Allocateurs

C++ 98, mais mis à jour de manière importante avec C++ 11

Allocateurs munis d'une portée (Scoped Allocators)

C++ 11

Opérateurs alignas et alignof

C++ 11

Annotations [[noreturn]] et [[carries_dependency]]

C++ 11

Annotation [[deprecated]]

C++ 14

Annotations [[fallthrough]], [[maybe_unused]] et [[nodiscard]]

C++ 17

Assertions statiques (static_assert)

C++ 11

Opérations atomiques

C++ 11

Mot clé auto

C++ 11, mais il s'agit d'une très ancienne caractéristique de C++

Les concepts

C++ 20, du moins on l'espère

Expressions constantes généralisées (mot clé constexpr)

C++ 11

Constructeurs de délégation

C++ 11

Constructeurs exposés du parent

C++ 11

Conteneurs et itérateurs

C++ 98

Mot clé decltype

C++ 11

Fonctions =default et =delete

C++ 11

Opérations emplace()

C++ 11

Énumérations fortes

C++ 11

Espaces nommés

C++ 98, mais C++ 11 pour les espaces nommés inline

Exceptions et spécifications d'exceptions

C++ 98

Mot clé explicit

C++ 98, mais C++ 11 pour les opérateurs de conversion

Expressions régulières

C++ 11

Mot clé contextuel final

C++ 11

Flux

C++ 98

Foncteurs

C++ 98

Fonctions inline

C++ 98

Boucles for sur des intervalles

C++ 11

Raffinements au mot clé friend

C++ 11

Le type std::function

C++ 11

Tables de hashage

C++ 11

Héritage

C++ 98

if/switch avec initialisation

C++ 17

if constexpr

C++ 17

Initialisation immédiate des attributs d'instance (NSDMI)

C++ 11

Initialisation uniforme

C++ 11

Internationalisation

C++ 98

Expressions λ

C++ 11

Listes d'initialisation ( std::initializer_list)

C++ 11

Littéraux binaires

C++ 14

Littéraux maison

C++ 11

Littéraux texte bruts

C++ 11

Mémoire (la gérer)

C++ 98

Méthodes qualifiées & et &&

C++ 11

Métaprogrammation

C++ 98

Clause noexcept

C++ 11

nullptr

C++ 11

Opérateurs (et leur surcharge)

C++ 98

Opérateurs de conversion explicites

C++ 11

optional<T>

C++ 17

Mot clé contextuel override

C++ 11

Pointeurs intelligents

C++ 98

Préprocesseur

C++ 98

Références sur des rvalue

C++ 11

Séparateurs d'unités

C++ 14

Standardisation de l'initialisation

C++ 11

Affectation déstructurante (Structured Bindings)

C++ 17

Surcharge de fonctions

C++ 98

Syntaxe unifiée des fonctions

C++ 11

Les templates

C++ 98

Les template typedefs

C++ 11

Les templates variadiques

C++ 11

Transtypage

C++ 98

Les uplets, ou tuples

C++ 11

Types

C++ 98

Variables templates

C++ 14

Outils et compilateurs

C++ 98

Trucs techniques d'ordre général

C++ 98

Unification des syntaxes d'appels de fonctions et de méthodes

C++ 17?

Bibliothèque <chrono>

C++ 11

Bibliothèque <random>

C++ 11

Bibliothèque <type_traits>

C++ 11

alignas et alignof

J'ai regroupé les considérations quant à l'alignement en mémoire dans ../Sujets/Developpement/Alignement.html

Algorithmes

Alias avec using

Depuis C++ 11, il est possible de remplacer les alias traditionnellement faits avec typedef par des alias faits avec using. Concrètement, par rapport à typedef, using n'a que des avantages, permettant de faire tout ce que son prédécesseur permettait, de le faire mieux, et de faire plus encore. Quelques exemples suivent.

Avec using (depuis C++ 11) Avec typedef (avant C++ 11)
using quantite = unsigned int;
typedef unsigned int quantite;
using ptri = int*;
typedef int * ptri;
using ptrf = int(*)(double);
typedef int (*ptrf)(double);
template <class V>
   using dictionnaire = map<string, V>;

Pas d'équivalent (pas de moyen pour définir partiellement un template)

template <class It>
   using val_t = typename iterator_traits<It>::value_type;

Pas d'équivalent (on faire un alias comme val_t pour une valeur de It choisie, mais pas un val_t paramétrique sur la base de It)

Quelques textes :

Allocateurs

Voir Gestion-memoire--Liens.html#allocateur pour des détails.

Allocateurs munis d'une portée (Scoped Allocators)

Voir Gestion-memoire--Liens.html#allocateur pour des détails.

Annotations

Voir ../Sujets/Divers--cplusplus/annotations.html pour des détails.

Argument-Dependent Lookup

L'Argument-Dependent Lookup (ADL), qu'on nomme parfois aussi le Koenig Lookup (pour Andrew Koening). Un truc à la fois utile et pas simple... Voir ../Sujets/Divers--cplusplus/argument_dependent_lookup.html pour plus d'informations.

auto et decltype

À propos des mots clés auto et decltype :

Bibliothèque <chrono>

Des éléments de la nouvelle bibliothèque d'outils de mesure du temps, <chrono> (enfin!) :

Bibliothèque <random>

Des éléments de la nouvelle bibliothèque stochastique, <random> (un ajout très apprécié au standard du langage!) :

Bibliothèque <type_traits>

Enfin, des traits sur les types, de manière standard :

Concepts

Les informations sur les concepts ont été regroupées sur ../Sujets/Divers--cplusplus/Concept-de-concept.html

constexpr

Expressions constantes généralisées, ou constexpr : ../Sujets/Divers--cplusplus/constexpr.html

Constructeurs de délégation

Il est possible depuis C++ 11 de faire en sorte qu'un constructeur délègue son travail à un autre constructeur.

Constructeurs exposés du parent

Il est possible depuis longtemps d'exposer, dans une classe dérivée, des services cachés d'un parent, à l'aide du mot-clé using. Depuis C++ 11, ce mécanisme est aussi applicable aux constructeurs.

Conteneurs et itérateurs

Les conteneurs et les itérateurs sont, avec les algorithmes standards, au coeur des pratiques contemporaines de programmation en C++.

Opérations emplace

Opérations emplace(). Raffinement des traditionnels insert(), push_front(), push_back() etc. des conteneurs standards de C++. L'idée est de construire les éléments à même le conteneur, plutôt que de construire une temporaire et de la copier dans le conteneur par la suite :

Énumérations fortes

À propos des énumérations fortes, un important raffinement en comparaison avec les énumérations « classiques » :

Espaces nommés

Certains disent aussi espaces de noms :

Une question qui revient souvent, en lien avec les espaces nommés, est le recours à un espace nommé anonyme, par exemple :

namespace {
   int glob = 3; // variable globale, ::glob de son vrai nom
}

Techniquement, les éléments d'un espace nommé anonyme ne sont pas visibles à l'édition des liens. Ceci remplace en quelque sorte la qualification static apposée traditionnellement sur les fonctions et les variables qui devaient être locales à un seul module objet dans un programme C.

L'avantage des espaces nommés anonymes sur la qualification static est qu'ils permettent aussi de cacher des types à l'éditeur de liens, alors que static ne s'applique qu'aux variables et aux fonctions.

Mot clé explicit

Depuis C++ 98, au moin), il est possible de qualifier un constructeur du mot clé explicit, pour forcer le code client à affirmer son intention et éviter certains accidents. Par exemple :

Sans constructeur qualifié explicit Avec constructeur qualifié explicit
struct Point
{
   float x, y;
   Point(float x, float y)
      : x{y}, y{y}
   {
   }
   Point()
      : Point{0.0f, 0.0f}
   {
   }
};
class Cercle
{
   float rayon;
   Point centre;
public:
   Cercle()
      : Cercle{Point{}, 10.f}
   {
   }
   Cercle(float rayon, const Point &centre = {})
      : rayon{rayon}, centre{centre}
   {
   }
   // ...
};
void f(Cercle);
int main()
{
   f(3.5f); // oups! Crée un Cercle{3.5f,Point{}}... Était-ce l'intention?
   f(Cercle{3.5f}); // Crée un Cercle{3.5f,Point{}}
}
struct Point
{
   float x, y;
   Point(float x, float y)
      : x{y}, y{y}
   {
   }
   Point()
      : Point{0.0f, 0.0f}
   {
   }
};
class Cercle
{
   float rayon;
   Point centre;
public:
   Cercle()
      : Cercle{Point{}, 10.f}
   {
   }
   explicit Cercle(float rayon, const Point &centre = {})
      : rayon{rayon}, centre{centre}
   {
   }
   // ...
};
void f(Cercle);
int main()
{
   f(3.5f); // illégal, car entraînerait la construction implicite d'un Cercle{3.5f,Point{}}
   f(Cercle{3.5f}); // Crée un Cercle{3.5f,Point{}}
}

Depuis C++ 11, le mot clé explicit peut aussi être apposé à des opérateurs de conversion.

Textes d'autres sources :

Expressions régulières

À propos des expressions régulières, enfin supportées de manière standard depuis C++ 11 :

Mot clé contextuel final

Le mot clé contextuel (et optionnel) final permet d'empêcher la spécialisation d'une classe ou d'une méthode polymorphique.

Classe dont on ne peut dériver Méthode qu'on ne peut spécialiser
class HauteurInvalide{};

class Carre final {
public:
   using size_type = unsigned short;
private:
   size_type hauteur_;
   static size_type valider(size_type hauteur)    {
      if (hauteur == 0) throw HauteurInvalide{};
      return hauteur;
   }
public:
   Carre(size_type hauteur)
      : hauteur_{valider(hauteur)}
   {
   }
   size_type hauteur() const noexcept {
      return hauteur_;
   }
   // etc.
};
struct Dessinable {
   virtual void dessiner(std::ostream &) const = 0;
   virtual ~Dessinable() = default;
};
//
// Ici, Forme implémente de manière tardive l'idiome NVI
// et ne veut pas que les enfants spécialisent dessiner()
// qui lui sert de point d'ancrage
//
class Forme : public Dessinable {
public:
   void dessiner(std::ostream &os) const final { // <-- ICI
      // valider des préconditions
      dessinerImpl(os);
      // valider des postconditions
   }
protected:
   virtual void dessinerImpl(std::ostream &) const = 0;
};

Flux

À propos des flux :

Foncteurs

À propos des foncteurs (aussi nommés Function Objects) :

Fonctions inline

Une fonction inline est telle que sa définition est visible au compilateur lorsque ce dernier rencontre le point d'appel, et permet au compilateur de remplacer le code à l'appel par le code appelé.

Boucles for sur des intervalles

Depuis C++ 11, il est possible de remplacer une répétitive comme celle-ci :

for(vector<T>::iterator it = begin(v); it != end(v); ++it)
   f(*it);

... par une répétitive comme celle-là :

for(T &elem : v)
   f(elem);

... ou encore, de manière plus générale, comme celle-là :

for(auto &elem : v)
   f(elem);

Il est possible de manipuler ainsi les éléments de tout conteneur pour lequel les fonctions globales std::begin() et std::end() s'appliquent. Il est possible de manipuler les éléments :

Par copie :

for(auto elem : v)
   f(elem);

Par référence :

for(auto &elem : v)
   f(elem);

Par référence-vers-const :

for(const auto &elem : v)
   f(elem);

Notez que le paramètre de la boucle représentant le conteneur sur les éléments duquel on itérera ne sera évalué qu'une seule fois. Ainsi, le programme suivant :

#include <iostream>
#include <vector>
using namespace std;

vector<int> f() {
    cout << "Appel de f()" << endl;
    vector<int> v { 1,2,3,4,5 };
    v.push_back(6);
    return v;
}

int main() {
    for(auto i : f())
       cout << i << endl;
}

... offrira la sortie suivante sur tous les compilateurs contemporains :

Appel de f()
1
2
3
4
5
6

Vous conviendrez que la nouvelle forme est plus concise que celle qui l'a précédée. Les boucles for sur des intervalles simplifient certaines pratiques usitées :

Raffiner friend 

std::function 

Le type std::function qui joue en C++ le rôle des délégués en C# :

Hashage

Des tables de hashage :

Héritage

L'héritage avec C++ :

if/switch avec initialisation

À partir de C++ 17, il devient possible de définir des variables à même un if ou un switch, de manière à ce que la portée de telles variables soit locale à la structure de contrôle. Par exemple :

Avant C++ 17 Depuis C++ 17
int n = f();
if(n < 0) {
   // n est accessible (bonne chose)
} else {
   // n est accessible (bonne chose aussi)
}
// n est accessible; peut être Ok, mais pas nécessairement
if(int n = f(); n < 0) {
   // n est accessible (bonne chose)
} else {
   // n est accessible (bonne chose aussi)
}
// n n'est plus accessible
char c = lire_touche();
switch(c) {
case '+' :
case '-' :
case '*' :
case '/' :
case '%' : cout << c << " est un operateur arithmetique" << endl; break;
// ...
}
// ici, c existe existe encore... peut-être Ok, peut-être pas
switch(char c = lire_touche(); c) {
case '+' :
case '-' :
case '*' :
case '/' :
case '%' : cout << c << " est un operateur arithmetique" << endl; break;
// ...
}
// ici, c n'existe plus

À ce sujet :

if constexpr

À partir de C++ 17, il devient possible d'évaluer des conditions à la compilation et d'exclure, sur la base de ces conditions, des blocs de code (qui doivent toutefois être bien formés). Par exemple :

//
// On veut un array<T,N> si N*sizeof(T) est moins de SEUIL bytes, et un vector<T> de N éléments sinon,
// ce qui explique le type de retour... qui sera déterminé par if constexpr
//
template <class T, int N, int SEUIL = 4096>
   auto creer_tampon_temporaire() {
      if constexpr(NB * sizeof(T) < SEUIL)
         return array<T,N> {};
      else
         return vector<T>(N);
   }

Pour en savoir plus :

Initialisation immédiate des attributs d'instance (non-static data member initialization, NSDMI)

Depuis C++ 11, il est possible d'initialiser des attributs d'instance dès leur déclaration.

J'ai mes réserves sur ce mécanisme, d'un point de vue pédagogique du moins.

Quand j'enseigne à des gens qui débutent dans la programmation, mes étudiant(e)s tendent à mal comprendre les constructeurs et leur rôle clé dans l'encapsulation; je constate qu'ils laissent souvent des attributs dans un état indéfini. Je suis conscient qu'affecter une valeur par défaut à chaque attribut « règle » ce problème, mais je ne suis pas certain que l'initialisation à deux endroits distincts (implicite, dans un constructeur) va entraîner chez eux de saines pratiques d'hygiène de programmation.

J'ai aussi l'impression que ce mécanisme favorise l'idée d'un objet dont les états n'ont pas à former un tout cohérent, et peuvent être initialisés de manière disjointe les uns des autres. C'est clairement vrai pour certains types, mais pas pour d'autres (une date, par exemple, si elle représente un triplet {jour,mois,année}, comprend une dépendance claire entre les valeurs de ses états). Pour un type comme un Point tridimensionnel, si l'on accepte que les valeurs x,y,z n'aient pas d'invariants en propre à respecter, on pourrait imaginer une application de ce mécanisme. Cela donnerait :

Sans initialisation immédiate Avec initialisation immédiate
struct Point {
   float x, y, z;
   constexpr Point() noexcept : Point{0.0f, 0.0f, 0.0f} {
   }
   constexpr Point(float x, float y, float z) : x{x}, y{y}, z{z} {
   }
   // ... etc.
};
struct Point {
   float x = {}, y = {}, z = {};
   constexpr Point() = default;
   constexpr Point(float x, float y, float z) : x{x}, y{y}, z{z} {
   }
   // ... etc.
};

Notez que le = default est nécessaire à droite, du fait que le constructeur paramétrique fait disparaître le constructeur par défaut qui aurait autrement été généré de manière implicite.

Je suis un partisan des petites classes (et des petites fonctions) qui font une et une seule chose. J'aime bien que ce soit simple, avec peu d'attributs. Peut-être que les gens qui font des objets très complexes, sortes d'amas d'états globaux, profitent plus d'une fonctionnalité comme celle-là. La perspective des gens qui oeuvrent sur le langage, et qui cherchent à être un peu plus agnostiques que nous face aux pratiques des gens qui utilisent le langage, est peut-être teintée par le souci d'aider dans de tels cas. Mais je spécule... Je changerai peut-être d'idée éventuellement, ça arrive!

Initialisation uniforme

Avant C++ 11, l'initialisation des objets en C++ prenait une forme variant significativement selon le type. Entre autres, pour initialiser un tableau d'entiers avec une séquence connue a priori de valeurs, on aurait écrit :

int tab[] = { 2, 3, 5, 7, 11 };

... alors que pour initialiser un vector<int>, il aurait fallu faire quelque chose comme ceci :

vector<int> v;
v.push_back(2);
v.push_back(3);
v.push_back(5);
v.push_back(7);
v.push_back(11);

... ou ceci :

int tab[] = { 2, 3, 5, 7, 11 };
vector<int> v;
for(auto p = begin(tab); p != end(tab); ++p)
   v.push_back(*p);

... ou encore ceci :

int tab[] = { 2, 3, 5, 7, 11 };
vector<int> v(begin(tab), end(tab));

...ce qui n'est pas élégant. De plus, dans certains cas, la syntaxe reposant sur des parenthèses pour appeler un constructeur peut mener à des ambiguïtés grammaticales : par exemple, à l'appel d'une fonction de signature T f(T);, passer un T par défaut pourrait s'écrire f(T()), or ceci peut signifier soit passer un T par défaut à f(), soit passer un appelable sans paramètre et retournant un T en paramètre, deux choses généralement bien, bien différentes.

Depuis C++ 11, on peut appeler cette fonction f() avec l'écriture f(T{}), où les accolades suppriment l'ambiguïté syntaxique, et il est possible d'initialiser nos conteneurs de la même manière que l'on initialiser un tableau brut, soit :

vector<int> v = { 2, 3, 5, 7, 11 };
// ... ou encore ...
vector<int> v{ 2, 3, 5, 7, 11 };

Internationalisation

L'internationalisation (on écrit aussi i18n, car il y a 18 lettres entre le « i » et le « n ») :

Expressions lambdas (λ)

Les λ, qui allègent tellement les tâches courantes :

Listes d'initialisation ( std::initializer_list)

J'ai groupé l'information à ce sujet dans ../Sujets/Divers--cplusplus/initializer_lists.html

Littéraux binaires

Ils est maintenant possible d'exprimer des littéraux binaires en C++. Par exemple, les expressions ci-dessous décrivent le même nombre :

Décimal Décimal avec séparateurs Octal Hexadécimal Binaire
1234567890
1'234'567'890
11145401322
499602d2
01001001100101100000001011010010

Littéraux « maison »

J'ai regroupé dans ../Sujets/Divers--cplusplus/litteraux_maison.html l'information sur les littéraux maison, par lesquels il devient possible d'enrichir la gamme des littéraux du langage.

Littéraux textes bruts

Les littéraux texte bruts, très utiles à l'ère Internet, entre autres pour manipuler du texte balisé et des expressions régulières :

À titre d'exemple, ceci... ...est équivalent à cela
"yo\n\\t'sais\n\"genre\" style\n\ncomme"
R"(yo
\t'sais
"genre" style

comme)"

Ces deux écritures décrivent le même const char*. On peut préférer l'un ou l'autre, mais les deux options existent.

Mémoire (gestion)

Considérations de gestion de mémoire avec C++ :

Métaprogrammation

La métaprogrammation est un sujet chaud dans ce langage :

Méthodes qualifiées & et &&

Il est possible depuis C++ 11 d'appliquer les qualification & et && à des méthodes, en plus des qualifications const et volatile.

noexcept

La clause noexcept : ../Sujets/Developpement/Exceptions.html#noexcept

Opérateurs

Notez qu'il est très facile d'abuser de la surcharge d'opérateurs, etr certains des liens qui suivent donnent des trucs qui pourraient mener à du code plus difficile à utiliser ou brisant le principe de moindre surprise. Agissez avec discrimination, et souvenez-vous que le code est lu plus souvent qu'il est écrit!

Les opérateurs et leur surcharge :

Opérateurs de conversion explicites

Il est possible depuis C++ 11 de qualifier les opérateurs de conversion du mot-clé explicit, comme on pouvait déjà le faire pour les constructeurs paramétriques.

optional<T>

La bibliothèque standard de C++ 17 offre un type optional<T> permettant de représenter une valeur possible. Ce type se teste comme un booléen, et n'est « vrai » que s'il n'est pas vide. Sa valeur (si elle existe) est exposée par sa méthode value(). Ceci peut entre autres servir d'alternative aux exceptions, chose utile dans les cas où ce mécanisme n'est pas à propos. Par exemple :

optional <int> division_entiere(int num, int denom) {
   if (!denom) return {}; // optional<int> par défaut (vide)
   return { num / denom }; // optional<int> contenant le fruit de la division entière
}
// ...
int main() {
   int num, denom;
   if (cin >> num >> denom) {
      if (auto resultat = division_entiere(num, denom); resultat) { // Ok depuis C++ 17
         cout <<resultat.value() >> endl;
      }
   }
}

À ce sujet :

Mot clé contextuel override

Le mot clé contextuel (et optionnel) override permet d'expliciter l'intention, dans une classe dérivée, de spécialiser une méthode polymorphique d'une classe parent.

Pointeurs intelligents

J'ai groupé les informations portant sur les pointeurs intelligents dans ../Sujets/AuSecours/pourquoi_pointeurs_intelligents.html

Préprocesseur

Vous trouverez d'autres liens sur ce sujet dans la section commune aux langages C et C++.

Le préprocesseur est une chose à éviter en C++, mais parfois... :

Références sur des rvalue

Les références sur des rvalue (rvalue references) :

Séparateurs d'unités

Ils est maintenant possible d'exprimer des littéraux binaires en C++. Ceci peut améliorer la lisibilité, du moins si on l'utilise judicieusement. La séparateur utilisé est l'apostrophe. Ainsi, les écritures suivantes sont équivalentes :

Décimal Décimal avec séparateurs Octal Octal avec séparateurs Hexadécimal Hexadécimal avec séparateurs Binaire Binaire avec séparateurs
1234567890
1'234'567'890
011145401322
011'145'401'322
0x499602d2
0x49'96'02'd2
0b01001001100101100000001011010010
0b0100'1001'1001'0110'0000'0010'1101'0010

Standardisation de l'initialisation

À propos de la standardisation de l'initialisation, qui rapproche le langage de l'un de ses objectifs, soit celui de traiter tous les types sur un même pied :

Affectation déstructurante (Structured Bindings)

Avec C++ 17, il devient possible de déconstruire un struct ou un tuple retourné par une fonction en ses éléments constitutifs, de manière à faciliter l'accès à ces membres dans le contexte du code client. Par exemple :

Avant C++ 17 À partir de C++ 17
struct Point {
   int x, y;
};
const int LARGEUR_CARTE = 80;
// ...
Point voisin_ouest(const Point&);
// ...
bool peut_aller_ouest(const Point &pt) {
   auto prochain = voisin_ouest(pt);
   return prochain.y < LARGEUR_CARTE;
}
struct Point {
   int x, y;
};
const int LARGEUR_CARTE = 80;
// ...
Point voisin_ouest(const Point&);
// ...
bool peut_aller_ouest(const Point &pt) {
   auto [x,y] = voisin_ouest(pt);
   return y < LARGEUR_CARTE;
}

À ce sujet :

Lors de la rencontre du WG21 à Kona en 2017, nous avons discuté d'une dichotomie entre le texte du Core Language, qui utilisait le terme Decomposition Declarations pour l'expression en soi et le terme Structured Bindings pour les variables découlant de cette décomposition, car il y avait une volonté d'utiliser strictement Structured Bindings dans le texte pour réduire la confusion des programmeuses et des programmeurs lorsque celles ou ceux-ci verront apparaître un message d'erreur à la compilation.

Après quelques débats, mettant entre autres en valeur l'importance de distinguer l'expression et son résultat, nous avons convenu d'utiliser Structured Bindings et Structured Binding Declarations. Ces termes devraient être ceux que vous rencontrerez en pratique, mais sachez que Decomposition Declarations a été utilisé pendant un certain temps pour ce qui sera désormais nommé Structured Binding Declarations.

Surcharge de fonctions

C++ permet de distinguer deux fonctions sur la base d'un certain nombre de critères. En effet, dans ce langage, deux fonctions sont différentes si :

Cette caractéristique du langage complique quelque peu l'interopérabilité au niveau du code machine généré : les noms dans le code machine sont typiquement différents du nom dans le code source; en ce sens, interopérer avec du code écrit en langage C, où il n'est pas légal d'avoir deux fonctions distinctes du même nom, est plus simple. Il est toutefois possible de faire de petits miracles à l'aide de cette mécanique. Notez que plusieurs utilisent enable_if pour contrôler la sélection de fonctions par le compilateur lorsque celui-ci cherche à déterminer laquelle des fonctions serait la plus à propos pour un certain appel.

Syntaxe unifiée des fonctions

Il est possible depuis C++ 11 d'écrire une fonction dont le type suit la signature plutôt que de la précéder... Et c'est parfois très utile!

Les template typedefs

Il est possible depuis C++ 11 de définir des templates avec spécialisations partielles, ce qui permet d'alléger énormément certaines écritures lourdes (en particulier celles en lien avec les traits) :

Les templates variadiques

Les templates variadiques permettent de suppléer un nombre arbitrairement grand de paramètres à une fonction :

Transtypage

Le transtypage, aussi nommé conversions explicites de type ou encore – en anglais – les Type Casts, ou simplement Casts :

tuple

Les tuples, ou uplets en français :

Types

Questions de types, en particulier les types primitifs du langage :

Variables templates

Depuis C++ 14, il est possible de définir des variables génériques (en plus des types et des fonctions génériques, bien connues des programmeuses et des programmeurs C++ de manière générale). Ceci permet d'exprimer du code générique tel que :


template <class T>
   constexpr const T PI = T(3.1415926535897932384626433832795);
template <class T>
   T circonference_cercle(T rayon)
   {
      return T(2) * PI<T> * rayon;
   }

Textes d'autres sources :

À propos des mots clés contextuels

Depuis C++ 11, certains mots clés de C++ sont contextuels, au sens où ils ne sont des mots clés que lorsque placés à certains endroits dans un programme. Ceci est dû au risque élevé que ces mots apparaissent ailleurs dans du code existant. C++ est décrit par un standard ISO et qu'il ne s'agit pas d'un produit appartenant à une entreprise ou l'autre, briser le code existant lors d'une mise à jour du standard est une préoccupation importante du comité de standardisation.

Les mots clés contextuels incluent override et final.

Pour en savoir plus :

Unification des syntaxes d'appels de fonctions et de méthodes

Chacun à sa manière, en 2014, Bjarne Stroustrup et Herb Sutter ont proposé d'unifier (avec nuances de part et d'autre) les syntaxes obj.f(args) et f(obj,args), ce qui refléterait la pratique existante pour des cas comme begin(conteneur) et conteneur.begin().


Valid XHTML 1.0 Transitional

CSS Valide !