🔝 Retour au Sommaire
Niveau : Débutant
Prérequis : Section 1.1 (Histoire et évolution du C++), Section 2.1.3 (GCC 15 et Clang 20)
Objectifs : Comprendre le rôle du flag-std=, savoir choisir le bon standard pour un projet, connaître l'état du support compilateur en 2026, et distinguer les modes stricts des modes avec extensions.
Le C++ est un langage en évolution constante. Depuis 2011, le comité de standardisation ISO publie une nouvelle version du standard tous les trois ans : C++11, C++14, C++17, C++20, C++23, et désormais C++26 ratifié fin 2025. Chaque version apporte de nouvelles fonctionnalités, de nouveaux composants de la librairie standard, et parfois des changements dans le comportement de constructions existantes.
Le compilateur, lui, doit savoir quelle version du langage vous écrivez. C'est le rôle de l'option -std= : elle définit le contrat entre vous et le compilateur. "Je m'engage à écrire du C++23, et tu t'engages à accepter les fonctionnalités de C++23 — et uniquement celles-là."
Sans option -std= explicite, chaque compilateur utilise son propre défaut, qui évolue au fil des versions :
| Compilateur | Version | Standard par défaut |
|---|---|---|
| GCC 10 | 2020 | -std=gnu++14 |
| GCC 11–13 | 2021–2023 | -std=gnu++17 |
| GCC 14–15 | 2024–2026 | -std=gnu++17 |
| Clang 16–19 | 2023–2024 | -std=gnu++17 |
| Clang 20 | 2025–2026 | -std=gnu++17 |
En mars 2026, GCC 15 et Clang 20 utilisent tous deux -std=gnu++17 par défaut. Cela signifie que sans option explicite, les fonctionnalités C++20 et ultérieures — concepts, ranges, coroutines, std::format, std::print, std::expected — sont refusées par le compilateur :
// Ce code nécessite C++20 ou supérieur
#include <vector>
#include <algorithm>
#include <ranges>
int main() {
std::vector<int> v = {5, 3, 1, 4, 2};
auto pairs = v | std::views::filter([](int n){ return n % 2 == 0; });
// ...
}g++ main.cpp -o mainmain.cpp: In function 'int main()':
main.cpp:8:27: error: 'std::views' has not been declared
8 | auto pairs = v | std::views::filter([](int n){ return n % 2 == 0; });
| ^~~~~
L'erreur peut sembler déroutante : le header <ranges> est trouvé par le compilateur, mais son contenu (std::views, etc.) n'est pas déclaré car il est en mode C++17, où les ranges n'existent pas. La solution est explicite :
g++ -std=c++20 main.cpp -o mainC'est pourquoi spécifier le standard explicitement est une règle d'or. Ne dépendez jamais du standard par défaut du compilateur — il changera lors de la prochaine mise à jour, et votre code pourrait se comporter différemment ou activer des fonctionnalités que vous n'aviez pas prévues.
Vous avez remarqué que le standard par défaut est gnu++17, pas c++17. La différence est significative.
Ce mode active exactement les fonctionnalités définies par le standard ISO C++23, sans aucune extension. C'est le mode le plus portable : un code qui compile en -std=c++23 sans warning a de bonnes chances de compiler sur n'importe quel compilateur conforme C++23.
Ce mode active le standard C++23 plus les extensions spécifiques à GCC. Parmi les extensions les plus connues :
- Les expressions statement (
({ int x = 5; x * 2; })) qui permettent d'utiliser un bloc comme une expression. - Le type
__int128pour l'arithmétique 128 bits. - L'attribut
__attribute__((visibility("default")))pour contrôler l'export des symboles. - Les VLAs (Variable Length Arrays) en C++, hérités du C99 mais non standard en C++.
- Les
typeofet__typeof__(avant C++23 qui a introduit son propre mécanisme).
Ces extensions sont pratiques mais non portables. Un code qui utilise __int128 ne compilera pas sous MSVC. Un code qui utilise des VLAs en C++ produira un warning avec -Wpedantic.
Pour un projet neuf qui vise la portabilité, utilisez toujours le mode strict :
g++ -std=c++23 -Wall -Wextra -Wpedantic main.cpp -o mainLe flag -Wpedantic (section 2.6.1) renforce cette discipline en avertissant sur toute utilisation d'extension non standard. C'est la combinaison qui produit le code le plus portable.
Si votre projet cible exclusivement Linux et GCC/Clang, le mode gnu++ est acceptable. Mais même dans ce cas, le mode strict reste préférable : il vous oblige à écrire du C++ standard, ce qui facilite une migration future vers un autre compilateur ou une autre plateforme.
Chaque standard apporte des fonctionnalités qui changent concrètement la manière d'écrire du C++. Voici un aperçu centré sur l'impact pratique, avec les renvois vers les sections de la formation qui les traitent en profondeur.
C++17 est le standard "mature" par excellence : intégralement supporté par tous les compilateurs majeurs depuis des années, il constitue la base solide sur laquelle repose la majorité du code moderne en production.
Les apports les plus marquants de C++17 dans la pratique quotidienne sont les structured bindings, qui permettent de décomposer une structure ou un tuple en variables nommées d'un seul coup (auto [x, y, z] = get_coordinates();). Les types std::optional, std::variant et std::any enrichissent la palette d'outils pour la gestion des valeurs absentes, des unions typées et de l'effacement de type. Le header <filesystem> fournit une API portable pour la manipulation des fichiers et répertoires, remplaçant les appels POSIX bruts dans la plupart des cas. Les if constexpr permettent de l'élimination de code à la compilation dans les templates. Et les déductions de type pour les templates de classes (CTAD) suppriment le besoin de spécifier les paramètres de template dans de nombreux cas courants (std::vector v = {1, 2, 3}; au lieu de std::vector<int> v = {1, 2, 3};).
📎 Voir sections 12.1 (Structured Bindings), 12.2 (optional/variant/any), 19.1 (std::filesystem).
C++20 est souvent décrit comme le plus gros changement depuis C++11. Il introduit quatre fonctionnalités majeures que la formation couvre en profondeur.
Les concepts permettent de contraindre les templates avec des expressions lisibles, remplaçant les techniques SFINAE complexes par une syntaxe déclarative : template<std::integral T> au lieu d'un enable_if obscur. Les ranges introduisent un paradigme de pipelines fonctionnels pour manipuler les séquences de données (v | filter(is_even) | transform(square)). Les coroutines fournissent un mécanisme standardisé pour la programmation asynchrone et les générateurs. L'opérateur spaceship <=> simplifie radicalement l'implémentation des comparaisons. Et les modules promettent de remplacer le système d'inclusion par headers, bien que leur maturité reste un sujet en 2026.
C++20 introduit aussi std::span pour les vues sur données contiguës, std::jthread pour les threads auto-stoppables, et consteval pour les fonctions strictement compile-time.
📎 Voir sections 12.4 (Concepts), 12.5 (Ranges), 12.6 (Coroutines), 8.5 (Spaceship), 12.13 (Modules).
C++23 apporte des améliorations qui comblent les lacunes de C++20 et ajoutent des outils très attendus.
std::print et std::format standardisent le formatage de texte avec une syntaxe type-safe inspirée de Python, remplaçant avantageusement std::cout et printf. std::expected fournit un type de retour qui représente soit un résultat, soit une erreur — une alternative aux exceptions pour la gestion d'erreurs. std::flat_map et std::flat_set offrent des conteneurs associatifs ordonnés à mémoire contiguë, optimisés pour le cache. std::mdspan permet les vues multidimensionnelles sur des données linéaires. std::generator simplifie la création de coroutines productrices de valeurs. Et std::stacktrace rend les traces d'exécution accessibles de manière portable.
📎 Voir sections 2.7 et 12.7 (std::print), 12.8 (std::expected), 12.9 (flat_map/flat_set), 12.10 (mdspan), 12.11 (generator), 12.12 (stacktrace).
C++26 a été ratifié par l'ISO fin 2025. C'est un standard ambitieux qui introduit des fonctionnalités transformatives.
Les contrats (contracts) permettent de spécifier des préconditions, postconditions et assertions directement dans les déclarations de fonctions, offrant un mécanisme standardisé de programmation défensive. La réflexion statique (static reflection) donne au code la capacité d'inspecter ses propres types et structures à la compilation. Et std::execution (Senders/Receivers) standardise l'asynchronisme et remplace le modèle std::async/std::future.
Le support compilateur de C++26 est encore partiel en mars 2026. GCC 15 et Clang 20 implémentent les contrats (partiellement) et certaines autres fonctionnalités, mais la réflexion statique est encore en cours d'implémentation.
📎 Voir section 12.14 et ses sous-sections pour une couverture détaillée de C++26.
Choisir un standard ne sert à rien si le compilateur ne le supporte pas. Voici l'état du support sur Ubuntu en mars 2026 :
| Fonctionnalité | Standard | GCC 15 | Clang 20 |
|---|---|---|---|
| Structured bindings | C++17 | ✅ | ✅ |
| if constexpr | C++17 | ✅ | ✅ |
| Concepts | C++20 | ✅ | ✅ |
| Coroutines | C++20 | ✅ | ✅ |
| Modules | C++20 | ||
| Opérateur <=> | C++20 | ✅ | ✅ |
| consteval | C++20 | ✅ | ✅ |
| Multidimensional subscript | C++23 | ✅ | ✅ |
| Deducing this | C++23 | ✅ | ✅ |
| Contrats (Contracts) | C++26 | ||
| Réflexion statique | C++26 | 🔄 En cours | 🔄 En cours |
| Composant | Standard | libstdc++ (GCC) | libc++ (Clang) |
|---|---|---|---|
| std::optional, variant, any | C++17 | ✅ | ✅ |
| std::filesystem | C++17 | ✅ | ✅ |
| std::span | C++20 | ✅ | ✅ |
| std::ranges | C++20 | ✅ | ✅ |
| std::jthread | C++20 | ✅ | ✅ |
| std::format | C++20/23 | ✅ | ✅ |
| std::print | C++23 | ✅ (GCC 15+) | ✅ (Clang 19+) |
| std::expected | C++23 | ✅ | ✅ |
| std::flat_map / flat_set | C++23 | ✅ | |
| std::mdspan | C++23 | ✅ | ✅ |
| std::generator | C++23 | ✅ | |
| std::stacktrace | C++23 | ✅ | |
| std::execution (S/R) | C++26 | 🔄 En cours | 🔄 En cours |
⚠️ Ces tableaux reflètent l'état en mars 2026. Le support évolue avec chaque release des compilateurs. La section 12.14.5 détaille l'état du support C++26 et la section 2.1.3 couvre les nouveautés de GCC 15 et Clang 20.
Les modules C++20 méritent une mention spéciale. Bien que le standard les définisse depuis 2020, leur implémentation dans les compilateurs et les build systems est encore incomplète et fragile en 2026. GCC 15 et Clang 20 supportent la syntaxe de base (export module, import), mais l'intégration avec CMake reste en maturation, et l'interopérabilité entre compilateurs (modules compilés par GCC utilisés par Clang) n'est pas garantie.
En pratique, les modules sont utilisables pour des projets mono-compilateur, mais le modèle classique par headers reste la valeur sûre pour les projets qui doivent supporter plusieurs compilateurs ou distribuer des librairies.
📎 Voir section 12.13 (Modules C++20 : état en 2026) pour une analyse détaillée de la maturité.
Quand vous suspectez un problème lié au standard, plusieurs techniques permettent de vérifier ce qui est actif :
Le standard définit une macro prédéfinie __cplusplus dont la valeur indique le standard actif :
| Standard | Valeur de __cplusplus |
|---|---|
| C++17 | 201703L |
| C++20 | 202002L |
| C++23 | 202302L |
| C++26 | 202600L (provisoire) |
On peut l'interroger directement :
echo '__cplusplus' | g++ -std=c++23 -E -x c++ - | tail -1202302L
Cette macro est utile pour l'écriture de code compatible avec plusieurs standards :
#if __cplusplus >= 202302L
#include <print>
#define LOG(msg) std::println("{}", msg)
#elif __cplusplus >= 202002L
#include <format>
#include <iostream>
#define LOG(msg) std::cout << std::format("{}\n", msg)
#else
#include <iostream>
#define LOG(msg) std::cout << msg << '\n'
#endifÀ partir de C++20, le standard définit des feature test macros granulaires qui indiquent le support de fonctionnalités individuelles. Elles sont plus précises que __cplusplus car elles reflètent ce que le compilateur supporte réellement, pas seulement le standard qu'il prétend implémenter :
#include <version> // ou n'importe quel header standard
#ifdef __cpp_concepts
// Concepts disponibles (__cpp_concepts >= 202002L)
#endif
#ifdef __cpp_lib_expected
// std::expected disponible (__cpp_lib_expected >= 202211L)
#endif
#ifdef __cpp_lib_print
// std::print disponible (__cpp_lib_print >= 202207L)
#endifLes macros préfixées par __cpp_ concernent les fonctionnalités du langage, celles préfixées par __cpp_lib_ concernent les composants de la librairie standard. C'est le mécanisme le plus fiable pour le code portable, car il teste le support effectif plutôt que le numéro de standard.
# Lister toutes les feature test macros définies
echo | g++ -std=c++23 -dM -E -x c++ - | grep __cpp | sortLe choix du standard est un compromis entre les fonctionnalités souhaitées, la portabilité requise, et la maturité du support compilateur. Voici un guide de décision pragmatique.
C++23 est le choix recommandé. Il est intégralement supporté par GCC 15 et Clang 20 (à quelques exceptions près dans la librairie), et il offre des outils qui simplifient réellement le code quotidien : std::print pour l'affichage, std::expected pour la gestion d'erreurs, les conteneurs flat_* pour la performance. C'est le standard que cette formation utilise par défaut.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # Équivalent de -std=c++23 (pas gnu++23) C++20 est le choix conservateur. Il est pleinement supporté depuis GCC 12 et Clang 14, ce qui couvre les compilateurs disponibles sur Ubuntu 22.04 LTS et les distributions comparables. Il apporte déjà les fonctionnalités transformatives (concepts, ranges, coroutines) sans exiger des versions de compilateur très récentes.
Si le projet est en C++17, il n'y a pas d'urgence à migrer — C++17 est un standard parfaitement viable et complet. La migration vers C++20 ou C++23 se justifie quand une fonctionnalité spécifique apporte un bénéfice concret (concepts pour simplifier les templates, std::expected pour remplacer un système d'erreurs artisanal, etc.), pas comme une fin en soi.
g++ -std=c++26 main.cpp -o mainLe flag -std=c++26 est accepté par GCC 15 et Clang 20, mais le support est partiel. C'est approprié pour l'expérimentation et l'apprentissage des nouvelles fonctionnalités, pas pour du code de production qui doit être fiable et portable.
Le choix du standard ne se limite pas à "activer" des fonctionnalités nouvelles. Dans de rares cas, un changement de standard modifie le comportement de code existant. Quelques exemples notables :
En C++17, u8"hello" produit un const char*. En C++20, il produit un const char8_t* — un type distinct et incompatible. Du code qui compilait en C++17 peut échouer en C++20 :
const char* s = u8"hello"; // OK en C++17, erreur en C++20L'introduction de l'opérateur spaceship <=> en C++20 a changé les règles de résolution des opérateurs de comparaison. Dans de rares cas, du code qui compilait en C++17 peut devenir ambigu en C++20 si plusieurs opérateurs candidats existent.
C++23 a élargi les contextes où les conversions implicites sont autorisées dans les expressions if constexpr et static_assert, ce qui peut changer la sémantique de certaines métaprogrammations.
Ces cas de rupture sont rares et bien documentés dans les notes de migration des compilateurs. Mais ils justifient de tester votre code après un changement de -std= plutôt que de supposer que tout continuera de fonctionner.
Sur un projet complexe, différentes parties du code peuvent avoir des besoins différents. Votre code propre peut cibler C++23, tandis qu'une dépendance tierce exige C++17. En CMake, chaque target peut spécifier son propre standard :
# Votre librairie principale en C++23
add_library(mylib src/mylib.cpp)
target_compile_features(mylib PUBLIC cxx_std_23)
# Un wrapper de compatibilité en C++17
add_library(legacy_wrapper src/wrapper.cpp)
target_compile_features(legacy_wrapper PUBLIC cxx_std_17)
# L'exécutable hérite du plus élevé requis par ses dépendances
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib legacy_wrapper) La commande target_compile_features est préférable à set(CMAKE_CXX_STANDARD ...) pour les projets multi-targets, car elle propage automatiquement les exigences de standard à travers les dépendances.
Le flag -std= est le premier choix technique à faire sur un projet. Il détermine les fonctionnalités disponibles, influence la portabilité, et peut dans de rares cas modifier le comportement du code existant. La règle fondamentale est de toujours spécifier le standard explicitement — ne jamais dépendre du défaut du compilateur.
Pour un nouveau projet en 2026, C++23 offre le meilleur rapport entre fonctionnalités et maturité. Le mode strict (-std=c++23, pas gnu++23) combiné à -Wpedantic garantit la portabilité. Les feature test macros (__cpp_lib_*) sont le mécanisme le plus fiable pour adapter le code aux capacités réelles du compilateur.
En combinant cette option avec les warnings (section 2.6.1), le niveau d'optimisation (section 2.6.2), et les informations de débogage (section 2.6.3), vous disposez d'une configuration de compilation complète et maîtrisée :
# Profil de développement complet
g++ -std=c++23 -Wall -Wextra -Wpedantic -Werror -g -O0 main.cpp -o main
# Profil de production complet
g++ -std=c++23 -Wall -Wextra -Wpedantic -O2 -g -DNDEBUG main.cpp -o mainProchaine section : 2.7 — Introduction à
std::print(C++23), où nous mettrons en pratique le standard C++23 en découvrant le nouveau système d'affichage qui remplacestd::cout.
⏭️ Introduction à std::print (C++23) : Le nouveau standard d'affichage