IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

explication de syntaxe template


Sujet :

Langage C++

  1. #1
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut explication de syntaxe template
    Bonjour,
    Je veux écrire une fonction de ce genre


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    constexpr int foo(const int bar)
    {
       static_assert(bar<32, "Use a lower number please");
       return something_const;
    }

    Mais ça ne compile pas car
    error: non-constant condition for static assertion

    J'ai trouvé sur le net cette solution


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<int bar>
    constexpr int foo()
    {
       static_assert(bar<32, "Use a lower number please");
       return something_const;
    }
    Je crois avoir compris l'idée générale, néanmoins je ne comprends pas cette écriture.
    J'avoue que les templates, j'ai toujours eu du mal avec l'écriture.
    Peut-on m'expliciter la syntaxe ?

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    Billets dans le blog
    4
    Par défaut
    static_assert est vérifée à la compilation, une variable c'est un truc de l'exécution.
    Une méthode constexpr peut être appelée à la compilation mais existe aussi à l'exécution.
    Un paramètre template est géré à la compilation donc oui ça marche, m'enfin ça n'a pas grand chose à voir
    Si ta fonction est uniquement à utiliser à la compilation, depuis C++20 tu peux la marquer consteval.
    static_assert n'y sera toujours pas utilisable mais tu devrais pouvoir remplacer ça par un constexpr if et une exception.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    consteval int foo(int bar)
    {
      if constexpr (bar >= 32) throw "Use a lower number please"
       return something_const;
    }
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    Bonjour, j'avoue qu'il y a quelque chose qui m'échappe.

    Une méthode constexpr peut être appelée à la compilation mais existe aussi à l'exécution.
    ça me semble très largement remettre en cause l'intéret du truc.
    à quoi ça sert de déclarer que c'est important que l'expression soit évaluée compile-time comme une constante si au final elle peut ne pas l'être ?
    Il semble qu'il n'en soit pas ainsi mais je pensais que la fonction foo étant une constexpr, elle devait nécessairement être évaluable compile-time et que pour cela, ses paramètres devait nécessairement eux aussi des constexpr.
    donc, le paramètre bar devrait nécessairement être une constexpr aussi, et en conséquence, le static_assert devrait fonctionner dans tous les cas.

    Bon, Il semble qu'il n'en soit pas ainsi mais je n'en comprends vraiment pas la raison.


    Quoiqu'il en soit j'ai fini par trouver la solution en combinant template et macro.

    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
     
    #include <stdio.h>
    #include <stdint.h>
     
    template<uint8_t a>
    constexpr uint8_t foo()
    {
        static_assert(a<8, "utilisez un parametre inférieur à 8");
        return (uint8_t)(1<<a);
    }
     
    #define bar(x)  foo<(x)>()
     
    int main (void)
    {
        int n = 3 ;
        printf("%d\n", bar(2) );
        printf("%d\n", bar(5) );
        printf("%d\n", bar(12) );
        printf("%d\n", bar(n) );
        printf("fini\n");
        return 0;
    }
    Je commente mon propre code, n'hésitez pas à pointer les erreurs si il y en a ou à me dire si j'ai bien compris.

    De par la constitution des templates la fonction foo() est automatiquement déclinée en autant de versions différentes que l'on a utilisé de paramètre A et ce A est nécessairement connu à la compilation pour pouvoir instancier le bon code.

    sauf qu'au lieu d’appeler foo(4) ou foo(18) ou il faut désormais écrire foo<4>() ou foo<18>()

    ce qui équivaut en fait à appeler des fonctions différentes qui seraient du genre

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    constexpr uint8_t foo4()
    {
        static_assert(4<8, "utilisez un parametre inférieur à 8"); //ça passe à tout les coups !
        return (uint8_t)(1<<4);
    }
     
    constexpr uint8_t foo18()
    {
        static_assert(18<8, "utilisez un parametre inférieur à 8");  //ça pète à tout les coups !
        return (uint8_t)(1<<18);
    }
    Comme la syntaxe est peu lisible pour celui qui va se servir de la fonction, je rajoute une macro

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    #define bar(x)  foo<(x)>()
    Je trouve ça très crade comme pirouette mais ça marche.


    compilation
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    src/tstConstexpr.cpp: In instantiation of ‘constexpr uint8_t foo() [with unsigned char a = 12; uint8_t = unsigned char]’:
    src/tstConstexpr.cpp:21:20:   required from here
    src/tstConstexpr.cpp:8:20: error: static assertion failed: utilisez un parametre inférieur à 8
        8 |     static_assert(a<8, "utilisez un parametre inférieur à 8");
          |                   ~^~
    src/tstConstexpr.cpp:8:20: note: ‘(12 < 8)’ evaluates to false
    Ok, c'est le comportement attendu.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    src/tstConstexpr.cpp:12:25: error: no matching function for call to ‘foo<n>()12 | #define bar(x)  foo<(x)>()
          |                 ~~~~~~~~^~
    src/tstConstexpr.cpp:22:20: note: in expansion of macro ‘bar’
       22 |     printf("%d\n", bar(n) );
          |                    ^~~
    src/tstConstexpr.cpp:6:19: note: candidate: ‘template<unsigned char a> constexpr uint8_t foo()6 | constexpr uint8_t foo()
          |                   ^~~
    src/tstConstexpr.cpp:6:19: note:   template argument deduction/substitution failed:
    src/tstConstexpr.cpp:12:25: error: the value of ‘n’ is not usable in a constant expression
    parfait, ça interdit aussi de passer un paramètre non constant. C'est exactement ce que je veux.


    J'avoue que je trouve ça très très laborieux pour arriver à faire quelque chose qui me semblait évident au départ.
    Suis-je le seul à avoir cette impression ?


    En vrai, là je suis dans l'exercice de style et en temps normal j'aurais simplement fait une macro sans check ni rien
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    #define foo(x) ((uint8_t)(1<<x))
    Mais c'était intéressant d'apprendre pour utiliser ailleurs sur des cas ou c'est vraiment important de faire des vérifications à la compilation.

  4. #4
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut
    Perso, je n'utiliserais pas de macro, mais j'ai l'habitude des syntaxes telles que foo<8>().

    J'avoue que je trouve ça très très laborieux pour arriver à faire quelque chose qui me semblait évident au départ.
    Suis-je le seul à avoir cette impression ?
    Il s'agit d'un simple test des templates ou tu as un vrai besoin derrière ?

  5. #5
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    En fait, c'est pas du tout pour tester les templates.
    C'est pour forcer l'évaluation à la compilation. et pouvoir utiliser static_assert sur des choses qui ne sont en pratique jamais des variables même si du point de vue de compilateur, ça n'est pas exclu.
    Et là, oui j'ai un vrai besoin derrière : C'est du code pour microcontroleur. (pour **petit** microcontroleur).

    J'ai donc besoin qu'un maximum de trucs soit vraiment évalués et verifiés compile-time pour 3 raisons.
    D'abord, j'ai pas envie que ça passe du temps à faire ces évaluations au runtime, la plupart du temps, c'est pipeau comme raison, mais il peut y avoir des fois où c'est vraiment critique.
    Deuxièmement j'ai pas envie d'encombrer la minuscule rom avec un algo qui peut être entièrement exécuté dans la phase de compilation. Dans mon cas, cet algo peut à lui seul bouffer 20% de la ROM et j'ai pas envie de l'évaluer moi même à la main. Il faut que le compilateur le fasse.
    Troisièmement, j'ai envie de vérifier la cohérence des paramètres dans la phase de compilation car si c'est foireux, Il n'y aura de toute façon aucun moyen de debugger au runtime sur la machine cible.

    donc

    Il faut que ça soit évalué compile-time. (c'est un MUST, pas un MAY ou un SHOULD)
    Il faut que ça couine compile-time si on donne des paramètres incohérents ou hors limites.
    Il faut que ça couine si c'est pas évaluable à la compilation, car ça révèle un mauvais usage.

    Evidemment je pourrais faire tout ça à grand coup de macros, ce qui est d'ailleurs la façon classique de faire.
    mais dans des algos pas tout a fait triviaux, ça deviens rapidement illisible. comme dans l'exemple ci dessous

    Code cpp : 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
    #define UBRR_VALUE (((F_CPU) + 8UL * (BAUD)) / (16UL * (BAUD)) -1UL)
    #if 100 * (F_CPU) > (16 * ((UBRR_VALUE) + 1)) * (100 * (BAUD) + (BAUD) * (BAUD_TOL))
      #define USE_2X 1
    #elif 100 * (F_CPU) < (16 * ((UBRR_VALUE) + 1)) * (100 * (BAUD) - (BAUD) * (BAUD_TOL))
      #define USE_2X 1
    #else
      #define USE_2X 0
    #endif
     
    #if USE_2X
      /* U2X required, recalculate */
      #undef UBRR_VALUE
      #define UBRR_VALUE (((F_CPU) + 4UL * (BAUD)) / (8UL * (BAUD)) -1UL)
      #if 100 * (F_CPU) > (8 * ((UBRR_VALUE) + 1)) * (100 * (BAUD) + (BAUD) * (BAUD_TOL))
        #warning "Baud rate achieved is higher than allowed"
      #endif
     
      #if 100 * (F_CPU) < (8 * ((UBRR_VALUE) + 1)) * (100 * (BAUD) - (BAUD) * (BAUD_TOL))
        #warning "Baud rate achieved is lower than allowed"
      #endif
    #endif /* USE_U2X */


    Pour moi, ce qui manque, c'est la possibilité écrire des fonction du genre

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr int foo(constexpr int param);//invalide
    Autrement dit, des fonction qui ne peuvent être utilisé QUE avec des params qui sont déjà des constantes connues.

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut
    Etant dev embarqué, je comprends très bien ces préoccupations que tu rencontres.

    Selon, la version de C++ que tu utilises, tu as des moyens plus ou moins puissants. Laquelle utilises-tu ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr int foo(constexpr int param);//invalide
    En même temps, ça n'a pas d'intérêt (de sens même ?) de rajouter un constexpr sur le paramètre. Les fonctions constexpr sont un peu chelou, car elles peuvent être exécutées à compile-time, comme à run-time, selon le contexte dans lequel on les utilise (en C++20, on peut d'ailleurs détecter ça : https://dev.to/pgradot/let-s-try-c-2...evaluated-34pb). Ton 2e paramètre empêcherait d'être runtime executable, et il ne sert à rien à compile-time car la fonction ne peut pas compiler si le paramètre n'est pas connu.

    Je vois que tu as une macro pour calculer un baudrate et t'assurer que tu as une valeur correcte. J'ai déjà fait des fonctions compile-time pour m'assurer de choses comme ça, par rapport à la fréquence CPU, aux diviseurs de clock, pour trouver des configurations de périphériques (genre SPI).

    Un dummy code qui ferait ce genre de chose (ça compile en C++14) :

    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
    29
    struct Config {
        unsigned int divider = 0;
        bool enable_sub_divider = false;
    };
     
    template <unsigned int baudrate>
    constexpr Config get_configuration() {
        static_assert(baudrate >= 9'600U, "Baudrate is too low");
        static_assert(baudrate <= 921'600U, "Baudrate is too high");
        constexpr unsigned int bus_frequency = 100e6;
        constexpr auto divider = bus_frequency / baudrate;
        if (baudrate > 115'200) {
            return {divider, false};
        } else  {
            return {divider, true};
        }
    }
     
    #include <iostream>
     
    int main(int argc, char* argv[]) {
        //get_configuration<1'200>(); // error: static assertion failed: Baudrate is too low
     
        //get_configuration<argc>(); // error: 'argc' is not a constant expression
     
        constexpr auto config = get_configuration<57600>();
        std::cout << config.divider << '\n';
        std::cout << config.enable_sub_divider << '\n';
    }
    Tout ce que tu peux faire en macro (à peu de choses près), tu peux le faire en plus joli avec des fonctions templates

    Après, si tu n'aimes pas la syntaxe get_configuration<1'200>() et que tu préfèrerais get_configuration(1'200), c'est sûr que cette solution ne te conviendra pas forcément...

    PS : j'avais espoir que ça doit possible avec une fonction consteval en C++20, mais on peut toujours pas utiliser ses paramètres dans des static_assert. Voir https://stackoverflow.com/questions/...tion-arguments (ça suggère d'ailleurs une macro pour s'en sortir : on finit toujours pas ressortir une bonne vieille macro du fond des tiroirs )

    PS bis : du coup en C++20 on peut arriver à la syntaxe que tu souhaites :

    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
    29
    30
    31
    struct Config {
        unsigned int divider = 0;
        bool enable_sub_divider = false;
    };
     
    #define CONSTEVAL_STATIC_ASSERT(c, msg) do { if (!(c)) throw msg; } while(false)
     
    consteval auto get_configuration(unsigned int baudrate) {
        CONSTEVAL_STATIC_ASSERT(baudrate >= 9600U, "Baudrate is too low");
        CONSTEVAL_STATIC_ASSERT(baudrate <= 921600U, "Baudrate is too high");
     
        constexpr unsigned int bus_frequency = 100e6;
        const auto divider = bus_frequency / baudrate;
        if (baudrate > 115200) {
            return {divider, false};
        } else  {
            return {divider, true};
        }
    }
     
    #include <iostream>
     
    int main(int argc, char* argv[]) {
        //get_configuration(1'200); // error: expression '<throw-expression>' is not a constant expression
     
        //get_configuration(argc); // error: 'argc' is not a constant expression
     
        constexpr auto config = get_configuration(57600);
        std::cout << config.divider << '\n';
        std::cout << config.enable_sub_divider << '\n';
    }

  7. #7
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    Très très intéressant, votre code.

    J'emploie actuellement -std=c++11

    J'ai essayé de passer à c++14 (ou c++20) mais alors mon code ne compilait plus (l'édition de liens sort des undefined reference to `operator delete(void*, unsigned int)
    Je n'ai pas trop le temps de comprendre pourquoi tout de suite alors je vais rester en c++11.
    Je reviendrai ici après avoir enquêté.


    Après, j'avais jamais pensé à cet usage des templates. Dans me tête, le template, ça servait seulement à spécifier des types.
    En tout cas j'ai bien pris note de ton exemple de code.
    Il va falloir que je fasse des essais jusqu'à ce que j'intériorise ça.

    Merci bcp !

  8. #8
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    Je me répond tout seul.
    ça compilait pas parce-qu'en c++14 et ultérieurs il y a un delete avec 2 paramètres au lieu d'un seul


    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #if __cplusplus > 201400L //test si on est en cpp14 ou plus
    void operator delete(void* ptr, size_t size) {
        // depuis c++14 l'opérateur delete prends également la taille de l'objet en paramètre car c'est utile à certains allocateurs/déallocateurs.
        // En réalité comme on utilise malloc et free en "dessous" et que ce système d'allocation garde déjà trace de la taille des blocs alloués, 
        // alors on a pas besoin de ce paramètre supplémentaire.
        // On se contente donc juste d'appeler free avec le pointeur.
     
        free(ptr);
    }
    #endif

    problème réglé.

  9. #9
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    sinon, j'ai testé consteval sur le pc et c'est exactement ce que je veux.

    Par contre sur avr, j'ai pas c++20, ça s'arrête à c++17.
    dommage !

Discussions similaires

  1. Explication de syntaxe
    Par franculo_caoulene dans le forum VB.NET
    Réponses: 8
    Dernier message: 27/02/2009, 14h32
  2. Explication de syntaxe
    Par kaking dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 25/06/2008, 10h28
  3. [Tableaux] Explication de syntaxe
    Par n1portki dans le forum Langage
    Réponses: 1
    Dernier message: 01/12/2007, 00h06
  4. [débutant] explication de syntaxe
    Par cyrill.gremaud dans le forum Langage
    Réponses: 4
    Dernier message: 13/11/2006, 14h19
  5. [AS] Besoin d'explication de syntaxe
    Par mdtdamien dans le forum Flash
    Réponses: 2
    Dernier message: 17/12/2005, 13h39

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo