GCC 12 apportera de nouvelles fonctionnalités C++, des améliorations et les corrections de bogues,
annoncée pour avril 2022

La version 12.1 de la collection de compilateurs GNU (GCC) devrait être publiée en avril 2022. Comme chaque version majeure de GCC, cette version apportera de nombreux ajouts, améliorations, corrections de bogues et nouvelles fonctionnalités. GCC 12 est déjà le compilateur système de Fedora 36. GCC 12 sera également disponible sur Red Hat Enterprise Linux dans le Red Hat Developer Toolset (version 7) ou le Red Hat GCC Toolset (version 8 et 9).

Plusieurs propositions ont été implémentées dans GCC 12. Le dialecte par défaut dans GCC 12 est -std=gnu++17 ; pour activer les fonctionnalités C++23, les options de ligne de commande -std=c++23 ou -std=gnu++23 doivent être utilisées. (Cette dernière option autorise les extensions GNU). Voici, ci-dessous, les nouvelles fonctionnalités affectant le C++ :

Nom : gcc.png
Affichages : 41562
Taille : 10,8 Ko

Un certain nombre de constructions auparavant interdites sont maintenant autorisées, et certaines de ces caractéristiques peuvent potentiellement réduire la taille des programmes.

if consteval

C++17 a introduit l'instruction if constexpr. La condition dans if constexpr doit être une expression constante (elle est manifestement évaluée constante). Si la condition est évaluée à true, la branche else, si elle est présente, est écartée. Cela signifie que la branche else n'est pas du tout instanciée pendant la compilation, ce qui est un comportement différent d'un if ordinaire. Si la condition est évaluée à false, la branche true est également rejetée.

Si une fonction est déclarée constexpr, elle peut être évaluée ou non au moment de la compilation, en fonction du contexte. Pour offrir au programmeur une certaine visibilité sur le moment où la fonction est évaluée à la compilation, C++20 a introduit une nouvelle fonction de bibliothèque, std::is_constant_evaluated(), qui renvoie true si le contexte actuel est évalué à la compilation :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <type_traits>
 
int slow (int);
 
constexpr int fn (int n)
{
  if (std::is_constant_evaluated ())
    return n << 1; // #1
  else
    return slow (n); // #2
}
 
constexpr int i = fn (10); // does #1
int n = 10;
int i2 = fn (n); // calls slow function #2

C++20 a introduit le mot-clé consteval. Une fonction (éventuellement membre) ou un constructeur marqué comme consteval est une fonction immédiate. Les fonctions immédiates sont évaluées pendant la compilation et doivent produire une constante, sauf si l'appel à une fonction immédiate a lieu dans une autre fonction immédiate ; si ce n'est pas le cas, le compilateur produit une erreur. Le compilateur n'émet pas de code réel pour ces fonctions. Cependant, les règles du langage ne permettent pas au développeur de remplacer n << 1 dans le test précédent par un appel à une fonction consteval :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <type_traits>
 
int slow (int);
consteval int fast (int n) { return n << 1; }
 
constexpr int fn (int n)
{
  if (std::is_constant_evaluated ())
    return fast (n); // 'n' is not a constant expression
  else
    return slow (n);
}
constexpr int i = fn (10);

Pour résoudre ce problème, la proposition P1938R3 a introduit if consteval, que GCC 12 met en œuvre. if consteval permet au développeur d'invoquer des fonctions immédiates, comme illustré ici :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <type_traits>
 
int slow (int);
consteval int fast (int n) { return n << 1; }
 
constexpr int fn (int n)
{
  if consteval {
    return fast (n); // OK
  } else {
    return slow (n);
  }
}
 
constexpr int i = fn (10);

Notons qu'il est valide d'avoir if consteval dans une fonction ordinaire, non-constexpr. Notons également que if consteval nécessite { }, contrairement à l'instruction if ordinaire. Il y a un problème avec l'interaction entre if constexpr et std::is_constant_evaluated(), mais heureusement le compilateur peut détecter ce problème. La solution est examinée dans la section suivante Extended std::is_constant_evaluated in if warning.

auto(x)

GCC 12 implémente la proposition P0849, qui autorise auto dans un cast de style fonction, dont le résultat est une pure rvalue (prvalue) :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
struct A {};
void f(A&);  // #1
void f(A&&); // #2
A& g();
 
void
h()
{
  f(g()); // calls #1
  f(auto(g())); // calls #2 with a temporary object
}

Notons que auto(x) et auto{x} sont tous deux acceptés ; cependant, decltype(auto)(x) reste invalide.

Variables non-littérales dans les fonctions constexpr

GCC 12 implémente la proposition C++23 P2242R3, qui autorise les variables non littérales, les gotos et les labels dans les fonctions constexpr tant qu'ils ne sont pas évalués de manière constante. Ce comportement étendu est utile pour du code comme le suivant (extrait de la proposition) :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
#include <type_traits>
 
template<typename T> constexpr bool f() {
  if (std::is_constant_evaluated()) {
    return true;
  } else {
    T t; // OK when T=nonliteral in C++23
    return true;
  }
}
struct nonliteral { nonliteral(); };
static_assert(f<nonliteral>());

Cet exemple ne compile pas en C++20, mais il compile en C++23 car la branche else n'est pas évaluée. L'exemple suivant ne compile également qu'en C++23 :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
constexpr int
foo (int i)
{
  if (i == 0)
    return 42;
  static int a;
  thread_local int t;
  goto label;
label:
  return 0;
}

Opérateur d'indice multidimensionnel

GCC 12 supporte la proposition C++23 P2128R6, un opérateur d'indice multidimensionnel. Les virgules dans les expressions d'indices ont été dépréciées dans C++20 via la proposition P1161R3, et dans C++23 la virgule dans [ ] a changé de signification.

Le C++ utilise l'opérateur membre operator[] pour accéder aux éléments d'un tableau, ainsi qu'aux types de type tableau tels que std::array, std::span, std::vector et std::string. Toutefois, cet opérateur n'acceptait pas d'arguments multiples en C++20, de sorte que l'accès aux éléments des tableaux multidimensionnels était implémenté à l'aide d'opérateurs parenthèses d'appel de fonction tels que arr(x, y, z), et d'autres solutions de contournement similaires. Ces solutions de contournement présentent un certain nombre d'inconvénients. Pour atténuer les problèmes liés à leur utilisation, C++23 permet à l'opérateur [] de prendre zéro ou plusieurs arguments.

En conséquence, ce scénario de test est accepté avec -std=c++23 :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
template <typename... T>
struct W {
  constexpr auto operator[](T&&...);
};
 
W<> w1;
W<int> w2;
W<int, int> w3;

Voici ce qui pourrait être un exemple plus clair, avec une mise en œuvre très simple :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct S {
  int a[64];
  constexpr S () : a {} {};
  constexpr S (int x, int y, int z) : a {x, y, z} {};
  constexpr int &operator[] () { return a[0]; }
  constexpr int &operator[] (int x) { return a[x]; }
  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
};
 
void g ()
{
  S s;
  s[] = 42;
  s[5] = 36;
  s[3, 4] = 72;
}
En tant qu'extension, GCC supporte toujours l'ancien comportement lorsqu'un opérateur d'indice surchargé n'est pas trouvé, bien qu'il émette un avertissement :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
void f(int a[], int b, int c)
{
  a[b,c]; // deprecated in C++20, invalid but accepted with a warning in C++23
  a[(b,c)]; // OK in both C++20 and C++23
 }

Notons qu'actuellement, operator[] ne supporte pas les arguments par défaut. Il semble toutefois que les arguments par défaut seront autorisés dans les futures versions. Si et quand l'ajustement proposé sera accepté, l'exemple suivant sera autorisé :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
struct X {
  int a[64] ;
  constexpr int& operator[](int i = 1) { return a[i] ; }
} ;

elifdef et elifndef
En C et C++, les directives de prétraitement #ifdef et #ifndef sont un « succès syntaxique » pour #if defined(something) et #if !defined(something). Étonnamment, les autres variantes de ces directives n'avaient pas les mêmes raccourcis. Pour corriger cette omission, les concepteurs du C et du C++ ont accepté les propositions N2645 et P2334R1, respectivement. GCC 12 implémente les deux propositions, de sorte que l'exemple suivant se compile correctement :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
#ifdef __STDC__
/* ... */
#elifndef __cplusplus
#warning "not ISO C"
#else
/* ... */
#endif

Veuillez noter que, pour compiler cet exemple sans erreur en C++20 et antérieur, vous devez activer les extensions GNU. En d'autres termes, -std=c++20 provoque une erreur de compilation, mais -std=gnu++20 ne provoque qu'un avertissement pédant si -Wpedantic est également activé.

Déclaration d'initialisation étendue

GCC 12 implémente la proposition P2360R0 dans C++23, qui étend simplement un init-statement (utilisé dans les instructions if, for et switch) pour lui permettre de contenir une déclaration d'alias. En pratique, ce changement signifie que le code suivant est accepté :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
for (using T = int; T e : v)
  {
    // use e
  }

Corrections et améliorations internes
Les changements décrits dans cette section rendent GCC plus conforme aux changements récents de la norme, et permettent des comportements qui ne fonctionnaient pas correctement auparavant.

Changements dans la recherche d'opérateurs dépendants

GCC 12 a corrigé un problème où le compilateur effectuait une recherche non qualifiée pour une expression d'opérateur dépendant au moment de la définition du modèle au lieu du moment de l'instanciation. Cette correction correspond au comportement existant pour les expressions d'appels dépendants. Considérez le cas de test suivant qui démontre ce changement :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
 
namespace N {
  struct A { };
}
 
void operator+(N::A, double) {
  std::cout << "#1 ";
}
 
template<class T>
void f(T t) {
  operator+(t, 0);
  t + 0;
}
 
// Since it's not visible from the template definition, this later-declared
// operator overload should not be considered when instantiating f<N::A>(N::A),
// for either the call or operator expression.
void operator+(N::A, int) {
  std::cout << "#2 ";
}
 
int main() {
  N::A a;
  f(a);
  std::cout << std::endl;
}

Ce programme affiche « #1 #2 » lorsqu'il est compilé avec les versions 11 ou antérieures de GCC, mais GCC 12 affiche correctement « #1 #1 ». C'est parce qu'auparavant, seule l'expression call se résolvait en la surcharge #1, mais avec GCC 12, l'expression operator le fait aussi.

auto specifier pour les pointeurs et les références à des tableaux

GCC 12 supporte également le rapport de défaut DR2397, couvrant l'auto-spécificateur pour les pointeurs et les références aux tableaux. Ce changement supprime la restriction selon laquelle le type de l'élément du tableau ne peut pas être un type placeholder. Ceci permet du code comme :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
int a[3];
auto (*p)[3] = &a;
auto (&r)[3] = a;
Cependant, aucun des exemples suivants ne fonctionne (bien qu'un jour ils pourraient le faire) :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
auto (&&r)[2] = { 1, 2 };
auto arr[2] = { 1, 2 };
int arr[5];
auto x[5] = arr;

Cela ne fonctionne pas car l'auto-déduction est effectuée en termes de déduction d'argument de modèle de fonction, donc le tableau se transforme en pointeur.
Pliage de fonctions triviales

Un appel bien formé à std::move ou std::forward est équivalent à un cast. Mais parce que ces constructions sont implémentées comme des appels de fonction, le compilateur génère des informations de débogage qui persistent même après que l'appel soit inlined. Ce code supplémentaire est un gaspillage car il n'y a aucun besoin de déboguer de telles opérations. Par conséquent, GCC 12 élide les appels à certaines fonctions inline triviales (telles que std::move, std::forward, std::addressof, et std::as_const) en simples casts dans le cadre de la routine générale de pliage d'expression du front-end.

En conséquence, les informations de débogage produites par GCC pourraient être jusqu'à 10 % plus petites, tout en améliorant le temps de compilation et l'utilisation de la mémoire de GCC. Ce comportement est contrôlé par une nouvelle option appelée -ffold-simple-inlines. Correction de la spécification trop permissive de l'initialisation directe de liste d'enum.

GCC 12 implémente le rapport de défaut DR2374, qui interdit, par exemple, l'initialisation de liste directe d'une énumération scopée à partir d'une énumération scopée différente :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
enum class Orange;
enum class Apple;
Orange o;
Apple a{o}; // error with GCC 12

Restrictions sur les arguments de modèle non typés dans les spécialisations partielles
Auparavant, une restriction trop stricte empêchait certaines utilisations de paramètres de modèles comme arguments de modèles. Cette restriction a été rectifiée en réponse au rapport de défaut DR1315, et GCC implémente maintenant ces utilisations. Par conséquent, l'utilisation plausible suivante d'un paramètre de modèle comme argument de modèle se compile correctement :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
template <int I, int J> struct A {};
template <int I> struct A<I, I*2> {}; // OK with GCC 12

Substitutions dans les paramètres de fonction dans l'ordre lexical

La déduction des arguments des modèles C++ a subi quelques modifications lorsque le rapport de défaut DR1227 a spécifié que la substitution se fait dans l'ordre lexical, c'est-à-dire de gauche à droite. Le code suivant démontre l'effet que cela peut avoir :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
template <typename T>
struct A { using type = typename T::type; };
 
template <typename T> void g(T, typename A<T>::type);
template <typename T> long g(...);
 
long y = g<void>(0, 0); // OK in GCC 12, error in GCC 11
 
template <class T> void h(typename A<T>::type, T);
template <class T> long h(...);
long z = h<void>(0, 0); // error in GCC 12, OK in GCC 11

GCC 12 substitue les arguments dans l'ordre de gauche à droite et vérifie si un type substitué est erroné avant de le substituer au reste des arguments. Ainsi, pour g<void>(0, 0) le compilateur essaie de substituer void dans g(T, typename A<T>::type) et voit que la première substitution résulte en un paramètre invalide de type void. Cette substitution invalide est un échec de la SFINAE, donc la première surcharge est rejetée et celle de g(...) est choisie à la place. Cependant, pour h<void>(0, 0), le compilateur substitue d'abord void dans le paramètre typename A<T>::type. Cela produit une erreur matérielle, car l'instanciation de A<void> n'est pas un contexte immédiat.

GCC 11 et antérieurs effectuaient la substitution dans l'ordre de droite à gauche, donc la situation était inversée : g<void>(0, 0) résultait en une erreur de compilation, alors que h<void>(0, 0) compilait bien.

Vérification plus stricte des attributs sur les déclarations d'amis

Si une déclaration d'ami possède un attribut, cette déclaration doit être une définition, mais avant la version 12, GCC ne vérifiait pas cette restriction. De plus, un attribut C++11 ne peut pas apparaître au milieu du decl-specifier-seq :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
template<typename T>
struct S {
  [[deprecated]] friend T; // warning: attribute ignored
  [[deprecated]] friend void f(); // warning: attribute ignored
  friend [[deprecated]] int f2(); // error
};
S<int> s;

Source : Red Hat

Et vous ?

Quel commentaires posez vous sur cette analyse de Marek Polacek, DevOps chez Red Hat ?

Voir aussi :

Intel achève l'adoption de LLVM et mettra fin aux mises à jour des compilateurs C/C++ classiques, le compilateur Intel C/C++ basé sur LLVM présente un avantage de 41 % par rapport à gcc

CLion 2021.2 est disponible, l'EDI C/C++ multiplateforme de JetBrains simplifie les configurations de compilation et améliore le débogage, entre autres

JetBrains dévoile la feuille de route de CLion 2022.2 : un aperçu de ce qui vous attend dans la prochaine version majeure de l'EDI C/C++ multiplateforme