C++17 adapte le static_assert() aux usages

23
2
mar.
2018
C et C++

Un développeur annonce "C'est fini, Compilé c'est testé, Linké c'est livré. Face à son chef dubitatif, il ajoute "Le code est constexpr et les tests sont static_assert()"

Spécification technique

Le TS [N3928] permet d'utiliser le static_assert(expr) avec un seul paramètre. Avant il fallait fournir un second : le paramètre message.

Changement

C++17 permet d'écrire static_assert(condition) avec un seul paramètre. Avant, seule la fonction static_assert(condition, message) était disponible avec le second paramètre message obligatoire.

// Les static_assert avec un message vide étaient courants
static_assert(sizeof(int) == 4, "");

// L'usage (mauvaise pratique?) a influencé C++17
static_assert(sizeof(int) == 4);

Renommer en constexpr_assert() ?

Pour l’anecdote, cette fonctionnalité aurait bien pu s'appeler constexpr_assert() car constexpr exprime qui est évalué lors de la compilation. Donc constexpr est plus précis que static dans le nom constexpr_assert(). La fonctionnalité static_if s'est bien fait renommer constexpr_if (voir l'historique de P0292).

Compilé c'est testé, linké c'est livré

Soulignons que les mots-clés constexpr et static_assert() permettent au C++ de réaliser l'adage "Compilé c'est testé, linké c'est livré" comme illustré par le comic strip ci-dessous.

Un développeur annonce à son responsable "Compilé c'est Testé, Linké c'est Livré" puis explique que le code C++ est constexpr et les tests sont static_assert()

Comment faisait-on avant ?

Profitons de cette petite dépêche pour parler un peu de méta-programmation.

Progressivement, dans les années 1990, quelques développeurs commençaient à utiliser détourner le mot-clé template pour avoir des assert() à la compilation. Initialement, le template avait été conçu pour la programmation générique, et le voilà propulsé pour ce qui deviendra quelques années plus tard la méta-programmation.

template <bool>
class Assert;

template <>
class Assert<true>{};

La classe template Assert<bool> n'est définie que pour la spécialisation <true>. La spécialisation <false> est volontairement non définie pour pousser le compilateur à afficher une erreur si utilisée. Si le code source utilise Assert<false>, le compilateur affiche l'erreur suivante :

  • implicit instantiation of undefined template 'Assert<false>' pour Clang entre la v3.3 et la v3.9 ;
  • aggregate 'Assert<false> Nom_de_la_variable' has incomplete type and cannot be defined pour GCC entre la v4.4 et la v7.

Pour t'exercer, tu peux jouer avec ces deux exemples disponibles sur godbolt.org :

int melange (int n)
{
  Assert<sizeof(n) == 4>
    La_fonction_melange_ne_gere_que_les_entiers_sur_4_octets;

  return (n<<16) + ((n<<8) & 0xF0) + (n>>16);
}
struct S
{
  float f;
  int   i;
  bool  b;
  char  c;
  short s;
};

Assert<sizeof(S) == sizeof(S().f)
                  + sizeof(S().i)
                  + sizeof(S().b)
                  + sizeof(S().c)
                  + sizeof(S().s)> 
  La_structure_S_ne_doit_pas_avoir_de_padding;
  • # static / constexpr

    Posté par . Évalué à 3.

    Pour l’anecdote, cette fonctionnalité aurait bien pu s'appeler constexpr_assert() car constexpr exprime qui est évalué lors de la compilation. Donc constexpr est plus précis que static dans le nom constexpr_assert(). La fonctionnalité static_if s'est bien fait renommée constexpr_if (voir l'historique de P0292).

    Pour moi static c'est tout ce qui se passe avant l’exécution du programme. Par exemple : l'analyse statique de code consiste à analyser une base de code sans l'exécuter. Quelle est la subtilité avec consexpr ? constexpr représente le temps de la compilation, alors que le static peut être au chargement du programme en mémoire ?

    • [^] # Re: static / constexpr

      Posté par . Évalué à 6.

      Pour moi static c'est tout ce qui se passe avant l’exécution du programme.

      Bah oui, mais pas pour C++, pour qui static signifie une existance en dehors d'une classe instantiée. Le seul usage qui pourrait correspondre, c'est "static const", mais là, c'est plus le "const" qui est important.

      Une partie de la complexité de C++ vient des conventions de nommage, et c'est bien dommage, parce que c'est suffisamment complexe comme ça. La raison de la réutilisation ad nauseam des mêmes mot-clés est évidente (ça permet de limiter au maximum de casser du vieux code qui ne pouvait pas savoir que certains noms de variables pourraient être interdits dans le futur), mais certaines constructions du C++17 ont de quoi faire des nœuds au cerveau, avec des synonymies partielles particulièrement confuses (class <=> typename dans les déclarations de templates, class <=> struct dans les déclarations de classes, typedef <=> using, etc). Du coup, utiliser static avec une signification différente de ce qu'il a ailleurs en C++, ça ne va pas simplifier le système.

      • [^] # Re: static / constexpr

        Posté par . Évalué à 2.

        Bah oui, mais pas pour C++, pour qui static signifie une existance en dehors d'une classe instantiée. Le seul usage qui pourrait correspondre, c'est "static const", mais là, c'est plus le "const" qui est important.

        Pas forcément. Il y a pleins de choses qui vivent en dehors des classes et qui ne sont pas dis « static » (les fonctions libres, les templates,…). De plus des mots-clef comme static_cast() ne correspond pas à ta définition (et il est antérieur à l'introduction de constexpr).

        Du coup, utiliser static avec une signification différente de ce qu'il a ailleurs en C++, ça ne va pas simplifier le système.

        Redéfinir un mot utilisé dans le reste de l'industrie à tel point que l'on en réécris une partie du langage (on renomme des mots-clefs) ne va probablement pas aider. Renommer des mots clefs en gardant la compatibilité (donc avoir 2 mots clefs qui feraient aujourd'hui la même chose) c'est ce qui amène des class/struct

        • [^] # Re: static / constexpr

          Posté par . Évalué à 2.

          class et struct ne sont pas identiques. Il y avait au moins ces 2 différences en C++03 (j'imagine que ces comportement ont été conservés dans les versions plus récentes, sans certitude) :

          Member of a class defined with the keyword class are private by default. Members of a class defined with the keywords struct or union are public by default.

          In absence of an access-specifier for a base class, public is assumed when the derived class is declared struct and private is assumed when the class is declared class.

      • [^] # Re: static / constexpr

        Posté par . Évalué à 4.

        typedef <=> using

        Pas très synonyme pour moi ça.

        using sert surtout à désambiguïser quelle version d'une fonction ou d'un type utilisé. À part pour les outils de boost qui sont hyper méga pénibles et illisibles sans ça ou un typedef, je ne m'en sers que dans des cas super rares (dont un wrapper une fois pour pouvoir debug du code faisant appel à la STL sans que gdb me dise "machin est optimisé, va donc te faire pendre", je ne savais pas encore pour "-D_GLIBCXX_DEBUG" via clang).

        typedef par contre permets de définir un type comme synonyme d'un autre. Ça peut servir à raccourcir le code, bien sûr, mais aussi à donner un nom sympa à une spécialisation particulière d'un template, ou un type sous-jacent d'un conteneur ou itérateur maison.

        Exemple pour le coup de la spécialisation:

        template <typename TYPE, TYPE INVALID_VAL, void(*DTOR)(TYPE)>
        struct unique_resource
        {
        //je vous passe l'implémentation, je pense le nom parlant et l'idée assez simple?*
        };
        
        void file_close( int fd )
        {
          close( fd ); //je sais, c'est pas bien d'ignorer la valeur de retour, un jour je ferai une version qui permette une gestion d'erreurs plus clean
        }
        
        void clean_ogl_texture( GLsizei tex )
        {
          glDeleteTextures( 1, &tex );
        }
        
        typedef unique_resource<int,-1,file_close> fd_t;
        typedef unique_resource<GLsizei,-1,clean_ogl_texture> gl_tex_t;

        *: mais au pire je peux en mettre, c'est pas un problème.

        • [^] # Re: static / constexpr

          Posté par (page perso) . Évalué à 4. Dernière modification le 02/03/18 à 15:02.

          typedef par contre permets de définir un type comme synonyme d'un autre.

          ´using´ aussi:

          using fd_t = unique_resource<int,-1,file_close>;
          using gl_tex_t = unique_resource<GLsizei,-1,clean_ogl_texture>;

          Ce code est strictement equivalent à tes typedef. Et c'est pour ça que arnaudus avançait qu'ils était synonymes

          Et P.S: il n'y a aucune spécialisation dans ton exemple. (La spécialisation est si tu faisait un truc du genre

          template<> struct unique_resource<int,-1,file_close> 
          { /*... some specialized implementation goes here ... */ };
          • [^] # Re: static / constexpr

            Posté par . Évalué à 2.

            Pas faux.

            Et pour le ps, je ne savais pas trop quel autre terme utiliser, je l'admets. Pas instanciation non plus… bref.

        • [^] # Re: static / constexpr

          Posté par . Évalué à 7.

          Pas très synonyme pour moi ça.

          Je ne voulais pas dire qu'ils étaient synonymes dans l'absolu, parce qu'évidemment ils ne sont pas interchangeables dans tous les contextes. Je voulais juste mentionner le fait que C++ est un langage très complexe, que les concepts sous-jacents sont souvent un peu perchés, mais qu'en plus, on ajoute la difficulté d'avoir des mot-clés peu spécifiques, partiellement synonymes (ou pire, presque synonymes), et recyclés à outrance. C'est toujours pareil, il est évidemment possible d'apprendre tout ça, mais ça serait aussi le cas si les mot-clés étaient gdqfdjkfbhj et kjsdbhdjfb… Même pour quelqu'un qui a l'habitude de coder, la première fois qu'on voit "typedef typename truc::machin bidule;", ça fait tout drôle… Comprendre les subtilités de C++17 quand on maitrise déja C++14, c'est une chose, mais apprendre tout ça d'un coup (oui, il y a des gens qui naissent régulièrement et qui n'ont pas avalé les constructions ésotériques une par une au fil des ans).

          • [^] # Re: static / constexpr

            Posté par . Évalué à 5.

            Non mais je te rassure, je trouve le C++ complexe aussi, et j'ai personnellement, en tant qu'auto-didacte du c++03, la certitude de ne pas tout comprendre au niveau des changements entre le 03 et 11. Alors le 17, sincèrement…

            Je voulais juste souligner que tous les choix faits ne sont pas sans raison, même si certains sont étranges en effet.
            Personnellement, je me dis qu'une solution pour contrer le fait de risquer de réutiliser des termes déjà employés par des programmes légitimes dans une version antérieure, serait d'exploiter le fait que les noms commençant par __ n'ont, je crois, pas le droit d'être utilisés pour autre chose qu'implémenter des outils de la lib standard (je ne fais que croire, parce qu'en pratique, quand je compile un truc qui inclue freetype j'ai une tétrachiée de warnings a ce sujet, parce que leurs headers guards commencent par ça, justement, mais bon, c'est du C, pas du C++).

            Enfin, je ne vais pas continuer sur mes suppositions, je risque de dire énormément de merde, et même si parfois ça fait du bien, j'ai plutôt envie d'en apprendre plus sur ce coup :)

  • # Grammaire

    Posté par (page perso) . Évalué à 7.

    s'est bien fait renommER

  • # En OCaml, compilé c'est livré.

    Posté par . Évalué à 1.

    C'est bon, je sors. ;)

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.