Bonjour tout le monde et bienvenue dans ce nouvel article consacré au if constexpr, arrivé avec C++17.
Des débuts mouvementés
A l’origine, les conditions compile-time ont été proposées avec la n3322 avec static_if et static_else en 2011.
Renommées constexpr_if et constexpr_else en 2014, pour devenir constexpr if et else en mars 2018, cette fonctionnalité subit ses derniers changements avec la p0292R2 pour devenir le if constexpr que nous connaissons avec C++17.
Cette fonctionnalité avait fait monter aux créneaux notre saint père Bjarne Stroustrup en 2014, qui avait un avis très péjoratif sur cette fonctionnalité, lui préférant les futurs concepts lite, qui devraient arriver avec C++20.
Dans la n3613, appuyée par la dernière slide de cette présentation, Bjarne présentait ses inquiétudes concernant ce qu’on appelait encore à l’époque static_if :
The static if feature recently proposed for C++ [1, 2] is fundamentally flawed, and its adoption would be a disaster for the language
Il appuie notamment le fait qu’il est possible de faire à peu prêt n’importe quoi avec (ce sur quoi je reviendrais à la fin de l’article), le comparant à goto, tout en rendant possiblement le travail des analyseurs statiques bien plus ardu. Si le document présente des problématiques réelles concernant l’ancien static_if, beaucoup ont considéré que les arguments techniques étaient soient inexacts, soit exagérés. Andrei Alexandrescu tient d’ailleurs les propos suivants sur ce post Reddit (que je vous invite à lire si ça vous intéresse) :
As always (well, almost always…) I stand by everything I write publicly. N3613 is unusually subjective, factually incorrect, and tendentious. It’s fine as a newsgroup article, blog post, or op-ed/opinion paper but quite a bit out of character for ISO C++. Saddeningly, it’s completely out of line as far as academic debate goes.
Usage et règles
L’un des principaux avantages de if constexpr est la simplification du code générique. Les références en bas de l’article contiennent un bon nombre de liens proposant des exemples d’utilisation.
if constexpr, contrairement à son homologue classique, n’est évalué qu’une seule fois au moment de la compilation. Il va choisir quelle branche sera compilée en fonction de la condition (qui doit être une expression connue à la compilation).
Dans un template, il n’est pas nécessaire que la branche inutilisée compile :
1 2 3 4 5 6 7 8 9 10 |
template<typename T> T foo(T t) { if constexpr (std::is_integral_type<T>::value) { return t * 2; } else { return t.do_something(); } } |
Dans l’exemple ci-dessus, la fonction foo est spécialisée pour les types entiers, et utilise une fonction membre de T si T n’est pas de type intégral. Dans le cas d’un entier, la branche else contient t.do_something qui n’est pas valide mais ne pose pas de problème puisque la branche n’est pas choisie.
Note: le fait que la branche ignorée n’a pas à être valide n’est valable que lorsque if constexpr est à l’intérieur d’un template. De plus, il n’est pas autorisé que la branche soit invalide peu importe T (tout simplement parce qu’il n’existe dans ce cas aucune instanciation valide).
Cette règle se rapproche fortement de SFINAE, et le code ci-dessus aurait pu être écrit pré-C++17 à l’aide d’un peu plus de lignes. Par exemple :
1 2 3 4 5 6 7 8 9 10 11 |
template<typename T> std::enable_if_t<!std::is_integral<T>::value, T> foo(T t) { return t.do_something(); } template<typename T> std::enable_if_t<std::is_integral<T>::value, int> foo(T t) { return t * 2; } |
D’une manière générale, if constexpr permet de remplacer les codes génériques utilisant la SFINAE. Dans la page sur std::visit de cppreferences.com, on peut voir un exemple de code tirant partie de cette fonctionnalité :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 3. type-matching visitor: a lambda that handles each type differently std::cout << ". After doubling, variant holds "; std::visit([](auto&& arg) { using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, int>) std::cout << "int with value " << arg << '\n'; else if constexpr (std::is_same_v<T, long>) std::cout << "long with value " << arg << '\n'; else if constexpr (std::is_same_v<T, double>) std::cout << "double with value " << arg << '\n'; else if constexpr (std::is_same_v<T, std::string>) std::cout << "std::string with value " << std::quoted(arg) << '\n'; else static_assert(always_false<T>::value, "non-exhaustive visitor!"); }, w); |
Comme vous pouvez le constater, if constexpr est utilisable dans les lambdas. Figurez-vous qu’il est même possible de mixer les deux formes de if :
1 2 3 4 5 6 7 8 9 10 11 |
template<int i> void foo(bool b) { if constexpr(i < 2) std::cout << "Salut\n"; else if(b) std::cout << i << '\n'; else std::cout << "Yo\n"; } |
if constexpr est aussi compatible avec le init-statement de C++17 :
1 2 |
if constexpr(constexpr int i = 0; i == 0) do_something(); |
On pourrait imaginer remplacer la compilation conditionnelle basée sur les condition du préprocesseur par if constexpr, mais les deux ne sont pas équivalents.
Si la branche inutilisée de if constexpr n’a pas à être valide dans le cas d’un template, ce n’est pas le cas dans le cas général :
1 2 3 4 5 |
void foo() { if consexpr(false) x = 0; // erreur: l'identificateur x n'est pas défini. } |
Enfin, il est possible d’émuler if constexpr si votre compilateur n’est pas à jour. Je vous laisse ce lien sur stackoverflow.com où plusieurs solutions sont proposées :).
Voilà, c’est déjà la fin. Merci de m’avoir lu et je vous dit à bientôt !
Références et ressources
- [EN] cppreference.com – if statement
- [EN] open-std.org | p0292R2 – constexpr if: A slightly different syntax
- [EN] open-std.org | n3322 – A Preliminary proposal for a static if
- [EN] hackernoon.com | A tour of C++17 : if constexpr
- linuxfr.org | C++17 Branche à la compilation (if constexpr)
- [EN] meetingcpp.com | How if constexpr simplifies your code in C++17
- [EN] Simplify your code with ‘if constexpr’ in C++17
- [EN] stackoverflow.com | Constexpr vs macro
- [EN] stackoverflow.com | Difference between if constexpr vs if
- [EN] stackoverflow.com | Constexpr if alternative
You must log in to post a comment.