Introduction
La version 8.1 du compilateur GCC est sortie le 25 Avril 2018. Les nouveautés de cette version pour les différents langages sont disponibles ici. Dans ce post, nous allons voir les nouveautés qui concernent le langage C++.
Support expériemental de certaines fonctionalités du prochain standard C++2a
Le compilateur propose les options -std=c++2a
ou std=gnu++2a
pour utiliser
certaines fonctionalités du standard C++2a en cours de finalisation.
C++2a (langage)
Initialisation par défaut des membres de type bit
Les membres de classes/structures de type bit peuvent avoir leur
initialisation par défaut. Même si l’otpion -stc=c++2a
n’est pas utilisée,
le compilateur acceptera l’instruction avec un message d’avertissement.
default_init_bit_field.cpp:7:15: warning: default member initializers for bit-fields only available with -std=c++2a or -std=gnu++2a
int x : 3 = 2;
Capture [=, this]
Cette fonctionalité mineure autorise l’utilisation de la syntaxe suivante
[=, this]
qui était considérée comme une erreur. Elle est équivalente à la
syntaxe [=]
mais son autorisation permet de faire la différence plus
clairement avec la syntaxe [=, *this]
.
Les pointeurs vers des membres const par référence
Cette fonctionalité corrige un problème dans l’interprétation des pointeurs vers
membres qui sont const &
.
Avant de voir le changement, commençons par utiliser un membre ref qualified
struct foo
{
void f() &;
};
Cette déclaration interdit d’avoir un appel de ce type
foo{}.f();
Par contre on peut faire ceci:
struct foo
{
void f() const & {};
};
int main(int argc, char *argv[])
{
foo{}.f();
}
Par contre, lors de l’utilisation de .*
avec un objet temporaire (ou pour
être plus précis une rvalue), le langage interdisait d’avoir un pointeur
vers une fonction membre avec le qualifier &. Ceci donnait une incohérence
entre l’interprétation de deux instructions équivalentes.
struct foo
{
void f() const & {};
};
int main(int argc, char *argv[])
{
foo{}.f();
(foo{}.*(&foo::f))();
}
La compilation du programme ci-dessous donnait le résultat suivant:
error: pointer-to-member-function type 'void (foo::*)() const &' requires an lvalue
(foo{}.*(&foo::f))();
La norme c++2a permet de remédier à cette incohérence, et les deux syntaxes sont
acceptées. A noter que l’utilisation de l’option -std=c++2a
n’est pas
nécessaire avec gcc pour compiler le programme précédent.
Simplication des règles pour les boucles sur une plage (range-based loop)
Quand un type définit un membre begin
mais pas de membre end
(ou l’inverse),
l’utilisation des boucle de plage est impossible (jusqu’au prochain standard
C++20). Avec ce changement, on peut définir des méthodes begin/end
externes
et les utiliser.
Pour illustrer ce changement, considérons la classe suivante (avec les deux
opérations begin/end
définies)
struct foo{
foo() {
generate(&tab[0], &tab[10], [n = 0]() mutable { return n++; });
}
int *begin() { return &tab[0]; }
int *end() { return &tab[10]; }
int tab[10];
int past_last;
};
int main(int argc, char *argv[]) {
foo f;
for (auto i : f) { cout << i << "/"; }
cout << endl;
}
Si on enlève les deux méthodes et qu’on définisse des méthodes externes, le programme reste correct.
struct foo{
foo() {
generate(&tab[0], &tab[10], [n = 0]() mutable { return n++; });
}
int tab[10];
int past_last;
};
int *begin(foo &f) { return &f.tab[0]; }
int *end(foo &f) { cout << "y"; return &f.tab[10]; }
int main(int argc, char *argv[]) {
foo f;
for (auto i : f) { cout << i << "/"; }
cout << endl;
}
Par contre si on enlève seulement une des deux méthodes, le programme devient incorrect.
struct foo{
foo() {
generate(&tab[0], &tab[10], [n = 0]() mutable { return n++; });
}
// int *begin() { return &tab[0]; }
int *end() { return &tab[10]; }
int tab[10];
int past_last;
};
int *begin(foo &f) { return &f.tab[0]; }
int *end(foo &f) { return &f.tab[10]; }
int main(int argc, char *argv[]) {
foo f;
for (auto i : f) { cout << i << "/"; }
cout << endl;
}
Avec ce changement, il est possible d’avoir une des deux méthodes membres
begin/end
définie et d’utiliser les boucle de plage en définissant les
deux méthodes à l’extérieur de la classe.
Clarification de la déduction des règles de déduction des arguments des templates
Avec la déduction des paramètres des template, une incohérence est apparue avec les constructeurs acceptant une liste et en particulier lors de leur utilisation avec un seul élément.
Par exemple avec la définition ci-dessous, le type déduit est
vector<vector<int>
.
vector v{vector{1, 2, 3}};
Cette déduction peut être perçue comme incohérente lorsqu’une définition
similaire utilise le constructeur par copie comme ci-dessous (déduction de
tuple<int, int, int>
).
tuple t{tuple{1, 2, 3}};
Avec ce changement, lorsqu’un seul élément est présent dans la liste, le type
déduit est vector<int>
. La même chose s’applique au type list
.
C++2a (libstdc++)
- Implémentation expériementale des fonction
std::to_address
etstd::endian
. - Implémentation du type
std::experimental::source_location
: voici un exemple d’utilisation
#include <iostream>
#include <experimental/source_location>
using namespace std;
int main(int, char *[]) {
experimental::source_location l = experimental::source_location::current();
cout << l.file_name() << "/" << l.function_name() << "/" << l.line() << "/"
<< l.column() << endl;
}
Amélioration de la gestion des erreurs de compilation
Accès à un membre privé lorsqu’un accesseur est disponible
Cette fonctionalité permet si on accède à un membre privé d’afficher l’accesseur correspondant. Un exemple montrant son Utilisation
struct foo
{
public:
int get_x() const { return x; }
private:
int x;
};
int main(int, char*argv[]) {
foo f;
cout << f.x << endl;
}
gcc8_getter.cpp: In function 'int main(int, char**)':
gcc8_getter.cpp:15:15: error: 'int foo::x' is private within this context
cout << f.x << endl;
^~
gcc8_getter.cpp:10:9: note: declared private here
int x;
^~
gcc8_getter.cpp:15:15: note: field 'int foo::x' can be accessed via 'int foo::get_x() const'
cout << f.x << endl;
^~
get_x()
Utilisation d’un macro utilisée avant sa définition
Cette aide à la compilation permet d’indiquer si la macro a été utilisée avant sa définition.
const int x = 3 * PI;
#define PI 3.14
int main(int, char*argv[]) {
}
Cast
L’option -Wold-style-cast
permet d’afficher un message d’aide pour indique
quand utiliser static_cast
, ‘const_cast’ ou ‘reinterpret_cast’
struct base {};
struct foo : public base {};
int main(int, char *argv[]) {
{
base *b = new foo;
foo *f = (foo *)b;
delete b;
}
{
const foo f1;
foo &f2 = (foo &)f1;
}
}
gcc8_cast.cpp: In function 'int main(int, char**)':
gcc8_cast.cpp:8:21: warning: use of old-style cast to 'struct foo*' [-Wold-style-cast]
foo *f = (foo *)b;
^
--------
static_cast<foo *> (b)
gcc8_cast.cpp:13:22: warning: use of old-style cast to 'struct foo&' [-Wold-style-cast]
foo &f2 = (foo &)f1;
^~
---------
const_cast<foo &> (f1)
Erreur dans un bloc extern "C"
Lorsqu’une erreur à l’intérieur d’un bloc extern "C"
est détectée, le
compilateur affiche le début du bloc extern "C"
.
Arguments des templates non concordants
Lorsque le compilateur détecte une différence des types des paramètres d’un template, le compilateur met en évidence le type différent et remplace les paramètres concordants par trois points … dans le message d’erreur.
Il y a aussi une nouvelle option -fdiagnostics-show-template-tree
qui permet
d’afficher les templates qui ne correspondent pas sous forme d’arbre.
LIBSTDC++
- Implémentation de l’API système de fichier std::filesystem
- std::char_traits
et std::char_traits peuvent être utilisé dans des expressions constantes - Implémentation partielles des fonction std::to_chars et std::from_chars Ces fonctions intégrées à la norme C++17 ont été partiellement implémentées car elles ne supportent actuellement que les types entiers.
#include <array>
#include <charconv>
#include <system_error>
#include <iostream>
#include <string_view>
using namespace std;
int main(int, char*[]){
array<char, 10> str;
auto res = to_chars(str.data(), str.data() + str.size(), 42);
if (res.ec == errc()) cout << string_view(str.data(), res.ptr - str.data());
}
#include <array>
#include <charconv>
#include <iostream>
#include <string_view>
using namespace std;
int main(int, char*[]){
array<char, 10> str{"42"};
int res;
from_chars(str.data(), str.data() + str.size(), res);
cout << res << endl;
}
Divers
L’option -Wreturn-type activée par défaut
L’option -Wreturn-type
est activée par défaut, ce qui n’était pas le cas dans
gcc7
int foo(const bool b) {
if (b) return 1;
}
L’option -Wclass-memaccess incluse dans -Wall
Cette option de gestion des avertissements permet d’avertir lorsque des classes
complexes sont manipulées avec des fonctions de gestion de mémoire telle
memcpy
. Cette option est incluse dans l’option -Wall
struct foo1 {
int x = 0;
double y = 0;
};
struct foo2 {
int x;
double y;
virtual void f() {}
};
struct foo3 {
int x;
vector<int> v;
};
int main(int, char*[])
{
foo1 f1a, f1b;
memcpy(&f1a, &f1b, sizeof(foo1));
foo2 f2a, f2b;
memcpy(&f2a, &f2b, sizeof(foo2));
foo3 f3a, f3b;
memcpy(&f3a, &f3b, sizeof(foo3));
}
Le résultat avec l’option activée est le suivant (seul le type simple foo1 est accepté )
memaccess.cpp:28:36: warning: 'void* memcpy(void*, const void*, size_t)' writing to an object of type 'struct foo2' with no trivial copy-assignment; use copy-assignment or copy-initialization instead [-Wclass-memaccess]
memcpy(&f2a, &f2b, sizeof(foo2));
^
memaccess.cpp:11:8: note: 'struct foo2' declared here
struct foo2 {
^~~~
memaccess.cpp:31:36: warning: 'void* memcpy(void*, const void*, size_t)' writing to an object of type 'struct foo3' with no trivial copy-assignment; use copy-assignment or copy-initialization instead [-Wclass-memaccess]
memcpy(&f3a, &f3b, sizeof(foo3));
^
memaccess.cpp:17:8: note: 'struct foo3' declared here
struct foo3 {
^~~~