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

Normalisation C++ Discussion :

Proposition à envoyer avant le meeting de prague


Sujet :

Normalisation C++

  1. #1
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut Proposition à envoyer avant le meeting de prague
    Salut,

    Si je viens vers vous aujourd'hui, c'est parce que j'ai remarqué quelques comportements que je juge complètement anormaux de la part du compilateur lorsque l'on utilise les constexpr.

    Je me suis donc directement adressé à la liste de diffusion du SG7 (le groupe "réflexion" du commité) pour expliquer mon point de vue.

    Cela n'a pas été sans peine, car j'ai du défendre mon point de vue, mais, au final (je vous passe les détails) voici la réponse que l'on m'a faite:
    So, are you saying unsigned integer overflow/underflow in constexpr
    computations should make the expression non-constant (cf. [expr.const])?

    That's probably a workable suggestion; please write a paper
    and submit it to the pre-Prague mailing.
    Seulement, sur ce coup là, je ne sais pas trop bien comment m'y prendre. Du coup, ayant terminé la rédaction de ce papier, j'aimerais avoir votre avis sur le sujet avant de l'envoyer vers les pontes

    Si vous pouviez donc trouver un peu de temps pour le lire (attention, il y a quatre pages, en anglais) et me donner votre avis, je vous en serais très reconnaissant

    Merci d'avance à tout le monde
    Images attachées Images attachées
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  2. #2
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Première lecture très très rapide, j'ai reviendrai plus tard.

    ---------------
    Je sais pas si tu as accès à un correcteur pour faire le pdf, j'ai noté ces typos

    - wether prend un h-> whether
    - forme -> form
    - occures -> occurs
    - Anlnowledgments -> Acknowledgments (je ne sais plus si ça prend un s d'habitude)
    ------------

    Sur le fond.
    La compatibilité avec le C n'est pas "irrelevant" (je ne sais plus parler français)...
    En effet, une expression au fond constante qui va apparaître dans une tierce expression telle qu'une boucle ou un test doit être traité de la même façon en tant que pseudo-rvalue que ce soit en C ou en C++, ou que si ça finit dans une pseudo-lvalue constant: les surprises ne sont pas acceptables. Le même comportement doit se retrouver dans tous les cas

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    while (i != UMAX + 1) // C ou C++
     
    constexpr auto M = UMAX +1;
    while(i != M)
    J'ai peur que le changement que tu proposes casse beaucoup de code.

    Oui, les promotions, c'est pourri. Ou le defined overflow c'est pourri, mais on ne peut pas y toucher sinon le traitement des expressions constantes deviendrait contextuel, et ce n'est pas acceptable pour raison du principe de moindre surprise.

    En revanche, si sur les overflows constants d'unsigned (les signed, c'est UB, donc en s'en fout), les compilateurs donnent des résultats différents (je pense que tu peux donner un lien godbolt pour illustrer en plus du code que tu recopies dans le papier), alors il y a un vrai problème qui doit être adressé. Il n'est pas normal que le cas defined ne le soit plus une fois passé chez les constantes.

    Aussi, toujours si l'overflow constant d'unsigned était UB (ce qui me surprend), alors ton papier peut passer car au fond, tu passes à defined ~~> error. De plus dans ce cas tu devrais signaler clairement dans le résumé que tu proposes de rendre defined un cas UB pour des raisons de moindre surprise et que la situation était déjà source de problèmes de portabilité.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Première lecture très très rapide, j'ai reviendrai plus tard.

    ---------------
    Je sais pas si tu as accès à un correcteur pour faire le pdf, j'ai noté ces typos

    - wether prend un h-> whether
    - forme -> form
    - occures -> occurs
    - Anlnowledgments -> Acknowledgments (je ne sais plus si ça prend un s d'habitude)
    ------------
    Merci pour les typo
    Sur le fond.
    La compatibilité avec le C n'est pas "irrelevant" (je ne sais plus parler français)...
    En effet, une expression au fond constante qui va apparaître dans une tierce expression telle qu'une boucle ou un test doit être traité de la même façon en tant que pseudo-rvalue que ce soit en C ou en C++, ou que si ça finit dans une pseudo-lvalue constant: les surprises ne sont pas acceptables. Le même comportement doit se retrouver dans tous les cas
    Je comprend ton point de vue, et, au risque de te surprendre, je suis en partie d'accord avec toi: il ne faut pas casser le code ... runime

    Si ce n'est que, quand nous atteignons ce code, nous ne somme déjà plus dans le contexte qui nous occupe ici et qui est ... la définition d'une valeur constexpr.

    Si tu te place dans un contexte dans lequel tu défini une variable (i, en l'occurrence) rien ne s'oppose à la promotion / conversion de ta constexpr pour qu'elle s'adapte au type indiqué. (tu as d'ailleurs attiré mon attention sur une modification à apporter à la proposition de texte pour la norme, merci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    while (i != UMAX + 1) // C ou C++
     
    constexpr auto M = UMAX +1;
    while(i != M)
    i dans le code que tu présentes n'est absolument pas constexpr, que je sache. C'est UMAX qui l'est

    Quant à M, ben, désolé, mais c'est une valeur incompatible avec le type de UMAX, le compilateur n'a pas à accepter ce genre de chose.
    J'ai peur que le changement que tu proposes casse beaucoup de code.
    Oui, les promotions, c'est pourri. Ou le defined overflow c'est pourri, mais on ne peut pas y toucher sinon le traitement des expressions constantes deviendrait contextuel, et ce n'est pas acceptable pour raison du principe de moindre surprise.
    La définition d'une valeur contexpr est toujours contextuelle, au même titre que la définition d'une valeur énumérée...

    C'est, justement, en raison du principe de moindre surprise, qu'il faut donc s'assurer que le comportement du compilateur soit toujours identique en cas d'overflow/ underflow.
    En revanche, si sur les overflows constants d'unsigned (les signed, c'est UB, donc en s'en fout), les compilateurs donnent des résultats différents (je pense que tu peux donner un lien godbolt pour illustrer en plus du code que tu recopies dans le papier), alors il y a un vrai problème qui doit être adressé. Il n'est pas normal que le cas defined ne le soit plus une fois passé chez les constantes.
    Ben, justement, on parle de constante propres au compilateur, et, dans ce cas, un UB est impardonnable

    Aussi, toujours si l'overflow constant d'unsigned était UB (ce qui me surprend),
    C'est pourtant le cas...
    alors ton papier peut passer car au fond, tu passes à defined ~~> error. De plus dans ce cas tu devrais signaler clairement dans le résumé que tu proposes de rendre defined un cas UB pour des raisons de moindre surprise et que la situation était déjà source de problèmes de portabilité.
    Ah, de fait, je peux faire cela...

    Un grand merci pour ton retour
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Je te répond en coup de vent.
    Il n'y a aucune différence entre while( i != UMAX+1) et constexpr auto M = UMAX+1. Dans les deux cas, il y a une sous-expression UMAX+1 qui est constante et qui va être résolue par le compilateur. Dans le second cas on la stocke explicitement, alors que dans le premier on consomme sur place.

    Cette sous-expression a un type, et toute ta question est: quel doit être le type de cette sous-expression?

    De fait, refuser UMAX+1 devrait être fait partout, y compris dans la boucle qui est runtime (ou pas, elle pourrait être dans une sous fonction constexpr).

    C'est ce point qui va être la source de tous les accrochements. La déclaration constexpr, ce qu'elle dit (si je ne me trompe pas), c'est que l'on exige que l'expression UMAX+1 soit constante, et on veut lui donner un nom. Sans cette étape si UMAX est constante, alors UMAX+1 l'est aussi. Même si on ne l'exige pas. Et donc les règles de résolution que tu proposes devraient s'appliquer à cet endroit là. Et donc, il y a des risques de refuser de compiler des codes non portables (si UB) qui compilaient.


    J'ai pensé à un truc après, pour les histoires de boucles infinies. Une boucle infinie sans effets de bords est un UB. Attention que dans ta démonstration, l'UB observé ne soit pas celui induit par une boucle infinie.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Je te répond en coup de vent.
    Il n'y a aucune différence entre while( i != UMAX+1) et constexpr auto M = UMAX+1. Dans les deux cas, il y a une sous-expression UMAX+1 qui est constante et qui va être résolue par le compilateur. Dans le second cas on la stocke explicitement, alors que dans le premier on consomme sur place.
    Effectivement, on a toujours la même expression UMAX+1, et, toujours, dans le cadre d'une affectation.
    Mais le fait est que, avec la boucle while(i=UMAX+1), la donnée définie n'est -- a priori -- pas constante, sauf si l'on veut créer une boucle infinie(*), alors que avec l'expression constexpr auto overflow = UMAX+1;, nous définissons expressement une donnée nommée overflow comme devant être considérée comme une constante de compilation.

    Autrement dit, ce qui importe dans ces expressions, c'est bien le type (et surtout l'obligation de constance à laquelle il est soumis) de l'opérateur de gauche.

    (*)D'ailleurs, si i est non signé et de même type que UMAX, tu vas avoir un sérieux problème si ton objectif est de créer une boucle infinie, car UMAX +1 est sensé valoir 0 (en tout cas pour les unsigned int, ce qui est le seul comportement clairement défini au runtime). Or, 0 est classiquement évalué à ... false lorsqu'il est converti en booléen. Ta boucle ne sert donc à rien, vu qu'elle ne sera jamais exécutée.


    Cette sous-expression a un type, et toute ta question est: quel doit être le type de cette sous-expression?
    La réponse est simple : son type doit impérativement être celui de l'opérande qui se trouve à gauche de l'opérateur d'affectation.

    De fait, refuser UMAX+1 devrait être fait partout, y compris dans la boucle qui est runtime (ou pas, elle pourrait être dans une sous fonction constexpr).
    Ce serait effectivement le plus logique. Mais là, nous risquons effectivement de créer une incompatibilité majeure avec C et de casser pas mal de code

    C'est ce point qui va être la source de tous les accrochements. La déclaration constexpr, ce qu'elle dit (si je ne me trompe pas), c'est que l'on exige que l'expression UMAX+1 soit constante, et on veut lui donner un nom. Sans cette étape si UMAX est constante, alors UMAX+1 l'est aussi. Même si on ne l'exige pas. Et donc les règles de résolution que tu proposes devraient s'appliquer à cet endroit là. Et donc, il y a des risques de refuser de compiler des codes non portables (si UB) qui compilaient.
    Et c'est justement là tout le problème:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <type_traits>
    #include <limits>
    #include <iostream>
    #include <cstdint>
    using namespace std;
     
     
    constexpr uint32_t UI32Max = integral_constant<uint32_t, numeric_limits<uint32_t>::max()+1>::value;
     
    int main(){
        std::cout<<UI32Max::value<<"\n";
    }
    compile avec n'importe quel compilateur, et va afficher ... 0. Tout cela à cause d'une règle disant que UMAX+1 = 0 et qui ne devrait être applicable qu'au runtime, étant donné que, dans le cas présent, le développeur demande explicitement une donnée de type uint32_t. Aucune promotion / conversion ne devrait être faite lors de l'évaluation de l'expression uint32_t, numeric_limits<uint32_t>::max()+1 etant donné que le type principal de l'expression correspond en tout point au type de l'opérande de gauche.

    De mon point de vue, le compilateur devrait clairement dire au développeur (qui est sensé savoir ce qu'il fait) qu'il a choisi un type trop petit pour permettre la représentation de la valeur qu'il demande.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Sur le fonctionnement des compilos, la lvalue de destination n'a aucun impact et n'en aura jamais sur l'évaluation des expressions.

    La seule chose qui compte dans ton expression finale, c'est le numeric_limits<uint32_t>::max()+1, si tu veux autre chose, il faut caster le résultat de max. Et ça c'est spécifié: on cycle. Sauf si promotion....

    PS: si j'ai utilisé i != UMAX+1, c'est bien pour être sûr de ne pas avoir une condition toujours vraie à contrario de i < UMAX+1. Et il n'y a pas d'affectation. Juste une évaluation de sous-expression constante qui est la même que dans constexpr auto M = UMAX +1. L'évaluation de UMAX +1 ne peut pas et ne doit pas être contextuelle (avec ou sans affectation, employé dans une autre expression qui attend un truc ou pas). Si on commençait à faire ça, auto aurait des comportements différents de ce qu'il fait d'habitude. Et la règle est que la rvalue (constexpr ou pas) porte son type. C'est une règle simple d'induction de type dans un monde déjà assez complexe.

    Un test simple me montre que tous mes compilos sont d'accord (j'ai fait avec icc aussi, mais ça fait beaucoup côté écran): on cycle, sauf si la promotion fait boucler.
    https://godbolt.org/z/BfLMJj
    C'est le comportement nominal, et il ne faut pas qu'il change car il est "defined", et de fait, changer pourrait casser du code valide.

    Le problème avec std::integral_constant<T, std::numeric_limits<T>::max()+1>::value, c'est que MSVC fait comme si on écrivait std::integral_constant<T, T(std::numeric_limits<T>::max()+1)>::value et masque le narrowing, alors que les 2 autres râlent. Au mieux on peut avoir des warnings de ce dernier. N'est-ce pas le narrowing implicite des constexpr qui est UB en vrai et qu'il faudrait concrétiser comme une erreur? Ou alors le problème est que certaines implémentations de integral_constant font comme si on avait value = T{expr} et pas d'autres?
    https://godbolt.org/z/LLvNu-
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    PS: si j'ai utilisé i != UMAX+1, c'est bien pour être sûr de ne pas avoir une condition toujours vraie à contrario de i < UMAX+1. Et il n'y a pas d'affectation.
    au temps pour moi, je n'avais pas vu le !

    Juste une évaluation de sous-expression constante qui est la même que dans constexpr auto M = UMAX +1.
    C'est justement là que je ne suis pas d'accord: avec ta boucle while, tu es dans un contexte runtime et, encore une fois, je n'ai rien contre ce comportement dans un tel context.

    Mais ce contexte runtime est à l'opposé de ce que fait l'expression constexpr auto M = UMAX +1, parce que nous sommes dans un contexte de définition d'une expression constante et que M est forcément du type de UMAX. Il ne pourrait pas être autre chose, étant donné que UMAX est la seule donnée dont le type soit explicit.

    L'évaluation de UMAX +1 ne peut pas et ne doit pas être contextuelle (avec ou sans affectation, employé dans une autre expression qui attend un truc ou pas). Si on commençait à faire ça, auto aurait des comportements différents de ce qu'il fait d'habitude.
    Ben non...

    Comme tu l'as si bien fait remarquer, le type résultat de std::numeric_limits<un_type_entier>::max() est clairement le type entier en question, et, si tu veux l'utiliser sous une autre forme, tu dois caster le résultat de max.

    Et, du coup, ou bien tu te retrouve avec un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr auto n = static_cast<uint16_t>(std::numeric_limits<uint32_t>::max()+1;
    (avoue que c'est se faire du mal pour rien, non )
    ou bien tu assure la promotion / conversion en définissant clairement le type de l'opérante qui se trouve à gauche de l'opérateur d'affectation. Mais, du coup, tu n'utilise plus auto:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr uint64_t = std::numeric_limits<uint32_t>::max()+1; //promotion acceptée, car UMAX + 1 est une valeur admise pour le type uint64_t
    Et la règle est que la rvalue (constexpr ou pas) porte son type. C'est une règle simple d'induction de type dans un monde déjà assez complexe.
    Ben justement... Mais la règle n'a pas de raison de changer!

    La règle à changer tient à la manière dont une expression provoquant potentiellement un overflow / underflow est évaluée.

    Un test simple me montre que tous mes compilos sont d'accord (j'ai fait avec icc aussi, mais ça fait beaucoup côté écran): on cycle, sauf si la promotion fait boucler.
    https://godbolt.org/z/BfLMJj
    C'est le comportement nominal, et il ne faut pas qu'il change car il est "defined", et de fait, changer pourrait casser du code valide.
    N'est-ce pas le narrowing implicite des constexpr qui est UB en vrai et qu'il faudrait concrétiser comme une erreur?
    Peut-être...

    Mais le problème, c'est que les raisons de cet UB sont distillées dans de nombreuses clauses (dont les clauses relatives à la conversion, à la promotion et au comportement face à l'overflow / underflow).

    En tout état de cause, il me semble préférable de traiter ces causes plutôt que de s'évertuer à appliquer un emplâtre sur une jambe de bois en essayant d'améliorer les règles de narrowing

    Ceci dit, tes premières interventions m'ont inspiré quelques modifications (en plus de la correction typo )...

    Voici donc la "v2", qui sera transmise très prochainement à mon contact auprès du comité
    Images attachées Images attachées
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Mais ce contexte runtime est à l'opposé de ce que fait l'expression constexpr auto M = UMAX +1, parce que nous sommes dans un contexte de définition d'une expression constante et que M est forcément du type de UMAX. Il ne pourrait pas être autre chose, étant donné que UMAX est la seule donnée dont le type soit explicit.
    C'est là que nous ne sommes pas d'accord. Le type de M est celui de l'expression `UMAX+1`. Et cette expression doit avoir le même type en runtime comme en statique. Pour un compilo, il n'y a aucune différence ici.

    Pire, image une factorisation partielle (chose que je croise régulièrement) qui donne des
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    auto a = f(UMAX+1);
    ...
    constexpr auto M = UMAX+1;
    auto b = F(M);
    a et b doivent être identiques en type (f template et pure)

    Et, du coup, ou bien tu te retrouve avec un code proche de[...]
    (avoue que c'est se faire du mal pour rien, non )
    C'est ce que je fais en "dynamique", et je me ferai bien plus de mal avec des règles qui changent en fonction du côté dynamique ou statique.

    Là, je connais la règle, elle est unique, je sais ce que je dois faire. Et je ne vais pas me retrouver avec des changements du moment où j'ai l'épiphanie: "Eh! Mais ce truc est constant, ajoutons le `constexpr` qui manque".

    Je regarderai la V2 dans l'aprèm. A+
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    auto a = f(UMAX+1);
    ...
    constexpr auto M = UMAX+1;
    auto b = F(M);
    a et b doivent être identiques en type (f template et pure)
    Encore une fois, A et B ne sont pas constexpr, et donc:
    1. il se peut qu'ils ne soient pas du même type, vu que tu parle d'une fonction template (n'est-ce pas le but des fonction template)
    2. f peut prendre et renvoyer des types différents (du fait du template de la fonction et des mots clé auto éventuellement utilisés)
    3. f peut fournir un résultat différent pour a et pour b, étant donné que c'est l'objectif des paramètres de fonction: faire varier le résultat en fonction des valeurs transmise.

    Je ne vois donc pas pourquoi tu bloque là dessus


    C'est ce que je fais en "dynamique", et je me ferai bien plus de mal avec des règles qui changent en fonction du côté dynamique ou statique.
    Si ce n'est que la différence ne se fait pas entre dynamique et statique, mais entre dynamique et "constante déduite à la compilation".

    Nous n'en sommes même pas à déterminer si une donnée peut être déduite à la compilation, nous en somme à déterminer la manière dont cette valeur sera définie à la compilation en fonction du type qui permettra de la représenter.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #10
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Mais ce contexte runtime est à l'opposé de ce que fait l'expression constexpr auto M = UMAX +1, parce que nous sommes dans un contexte de définition d'une expression constante et que M est forcément du type de UMAX. Il ne pourrait pas être autre chose, étant donné que UMAX est la seule donnée dont le type soit explicit.
    Je ne suis pas d'accord sur ce point, si je prends un UCHAR_MAX, pourquoi je ne voudrais pas la promotion ? Je connais les règles, je sais ce que cela va faire. Ensuite, si 1 est une variable, quel est le "meilleur" type ? Mais pourquoi avoir une variable de type int ou mettre qui est finalement un int changerait quoi que se soit ?

    Plusieurs conflits arrivent également avec cette proposition:

    - Le type de l'expression ne peut plus être déterminé avec le type des opérantes: problème avec std::common_type.
    - Dans la même veine, problème avec decltype qui ne va pas retourner le même en utilisant des variables constpexr ou non ou des declval<T>. Ou alors le type est toujours le même, mais le résultat sera faux dans un contexte constexpr.
    - Que faire si une des opérantes est un type utilisateur qui surcharge + ?
    - Que faire avec toutes les autres règle de conversion implicite sur les types utilisateurs ?
    - Le problème soulevé par Luc. Je me permets un exemple bien plus pernicieux:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    constexpr auto foo(auto x) { return x+1; } // la même valeur avec le même type influence le résultat
     
    template<auto max>
    void bar(auto x)
    {
      using R = decltype(foo(max)); // constexpr, erreur possible ?
      R r = foo(x); // runtime, toujours ok
      // ....
      decltype(foo(max)) r2 = foo(x); // ???
    }
    Citation Envoyé par koala01 Voir le message
    Comme tu l'as si bien fait remarquer, le type résultat de std::numeric_limits<un_type_entier>::max() est clairement le type entier en question, et, si tu veux l'utiliser sous une autre forme, tu dois caster le résultat de max.
    Le problème n'est pas là, mais au niveau de l'expression qui inclut d'autres types avec des opérateurs: si tu veux le même type qu'un des opérateurs, `auto` n'est pas la solution. Ici, tu veux modifier les règles de promotion pour le type finale de l'expression soit différent. Cette proposition ne résout pas non plus les promotions depuis le même type INT_MAX + INT_MAX -> int ou long (long) ?

    À mon sens, plutôt qu'ajouter de la complexité avec un comportement différent dans des contextes différents, il faudrait plutôt créer des types spécifiques qui s'occupent eux-mêmes de bloquer ou étendre les promotions.

    Il y a juste un truc qui me choque, c'est le comportement de std::integral_constant<char, 999999> qui compile sur msvc et icc. J'ai bien plus l'habitude de voir une erreur. C'est plutôt sur ce point qu'il faudrait expliciter les règles (ce qui corrigerait le problème de index_impl de la proposition). Il y a le même problème avec if constexpr (2) que tout le monde accepte, sauf clang.

  11. #11
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Je ne suis pas d'accord sur ce point, si je prends un UCHAR_MAX, pourquoi je ne voudrais pas la promotion ? Je connais les règles, je sais ce que cela va faire.
    Simplement parce que tu n'as pas le choix!
    Entre les deux codes suivants, indépendamment de tout débordement et de toute notion de constante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    auto i = UCHAR_MAX;
    unsigned short j = UCHAR_MAX;
    Où se trouve la promotion

    Le type de i ne peut pas être autre chose, du fait de l'inférence de type, que ... unsigned char, vu que c'est le type normal de la valeur UCHAR_MAX, nous sommes bien d'accord

    Si promotion il doit y avoir, cela ne peut se faire qu'au travers d'un unisgned short. Et elle ne peut donc avoir lieu que pour j

    Autrement dit, tu ne peux pas avoir la promotion (le type unsigned short) et l'inférence de type en même temps, à moins bien sur de faire un cast explicite.

    Ensuite, si 1 est une variable, quel est le "meilleur" type ? Mais pourquoi avoir une variable de type int ou mettre qui est finalement un int changerait quoi que se soit ?
    Parce que l'on se trouve dans une situation dans laquelle nous voulons définir une constante de compilation, et que nous définissons en outre clairement le type de la donnée que l'on veut manipuler.

    Pourquoi y aurait-il promotion d'une valeur connue pour être de type unsigned char (comme UCHAR_MAX) alors que je dis explicitement que je veux un unsigned char dans un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr unsigned char i = UCHAR_MAX;
    Et, si tu fais jouer l'inférence de type, c'est pareil:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr auto i = UCHAR_MAX;
    Il ne peut y avoir de promotion de UCHAR_MAX, vu que j'ai explicitement dit que je voulais le type associé à cette valeur.
    Plusieurs conflits arrivent également avec cette proposition:

    - Le type de l'expression ne peut plus être déterminé avec le type des opérantes: problème avec std::common_type.
    Ben si, il est défini par le contexte dans lequel l'expression est exécutée, ce qui est tout à fait normal, étant donné qu'une expresion constexpr -- et c'est mis dans la norme -- soit statique soit inline.
    Citation Envoyé par norme 10.1.5 (dcl.constexpr)
    1- The constexpr specifier shall be applied only to the definition of a variable or variable template or the declarationofafunctionorfunctiontemplate. Afunctionorstaticdatamemberdeclaredwiththe constexpr specifier is implicitly an inline function or variable (10.1.6). If any declaration of a function or function template has a constexpr specifier, then all its declarations shall contain the constexpr specifier.
    - Dans la même veine, problème avec decltype qui ne va pas retourner le même en utilisant des variables constpexr
    Bien sur que si qu'il va retourner le même type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<typename T, typename U>
    auto add(T t, U u) -> decltype(t + u);
    Que T, U ou les deux soient constexpr ou non ne changera absolument rien à l'affaire

    ou non ou des declval<T>.
    Idem. Que que T soit constexpr ou non, le type représenté par T ne change pas.

    Je ne vois pas pourquoi cela poserait problème


    Ou alors le type est toujours le même(1), mais le résultat sera faux dans un contexte constexpr(2).
    (1) ben oui, il n'y a pas de raison qu'il en soit aurement
    (2)A priori, non, et, si cela arrive, c'est qu'il y a un dépassement de valeur (dans un sens ou dans l'autre) par rapport au type défini par decltype, et il est donc normal d'avoir une erreur de compilation

    - Que faire si une des opérantes est un type utilisateur qui surcharge + ?
    Si l'utilisateur défini l'opérateur + sous la forme d'une constexpr, il est normal qu'il en subisse les même contrainte: dépassement de valeur (dans un sens ou dans l'autre) interdit.

    - Que faire avec toutes les autres règle de conversion implicite sur les types utilisateurs ?
    On ne les modifie pas. On se contente en gros de les ignorer dans un contexte de constexpr. Point barre.

    Parce qu'en réalité, dans le contexte d'une constexpr, il n'y a que deux règles à prendre en compte:
    1- la conversion doit être explicite dans le contexte, que ce soit sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr auto i = static_cast<some_type>(/* ... */);
    ou sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr unsigned short j = /* ...*/;
    2- l'expression de droite ne peut pas provoquer de dépassement de valeur au niveau du type défni à gauche.
    - Le problème soulevé par Luc. Je me permets un exemple bien plus pernicieux:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    constexpr auto foo(auto x) { return x+1; } // la même valeur avec le même type influence le résultat
     
    template<auto max>
    void bar(auto x)
    {
      using R = decltype(foo(max)); // constexpr, erreur possible ? (1)
      R r = foo(x); // runtime, toujours ok (2)
      // ....
      decltype(foo(max)) r2 = foo(x); // ??? (3)
    }
    (1) Effectivement, une erreur à la compilation est toujours possible ici, tout dépendant
    1. du type effectif de max
    2. de la valeur de max par rapport à l'intervalle autorisée par son type

    (2) Dans le respect des règles définies, rien ne change: si c'était ok avant, c'est ok. Si cela amène un undefined behaviour (par exemple, parce que max n'est pas de type unsigned int), le même comportement indéfini continue à s'appliquer
    (3) on n'est pas dans un contexte constexpr, même si foo est déclarée comme telle: rien ne change

    Le problème n'est pas là, mais au niveau de l'expression qui inclut d'autres types avec des opérateurs: si tu veux le même type qu'un des opérateurs, `auto` n'est pas la solution. Ici, tu veux modifier les règles de promotion pour le type finale de l'expression soit différent. Cette proposition ne résout pas non plus les promotions depuis le même type INT_MAX + INT_MAX -> int ou long (long) ?
    A priori, je dirais:
    erreur pour affectation à donnée auto ou de type de taille plus petite ou égale à un int à cause d'un dépassement
    conversion pour affectation à donnée de tout type susceptible de représenter correctement la valeur de 2*INT_MAX
    À mon sens, plutôt qu'ajouter de la complexité avec un comportement différent dans des contextes différents, il faudrait plutôt créer des types spécifiques qui s'occupent eux-mêmes de bloquer ou étendre les promotions.
    On a déjà quinze type primitifs, répartis en deux grandes catégories (entier et réels) dont une catégorie se subdivise en deux saveurs se répartissant équitablement la plus grosse part du gâteau.

    Combien de type primitifs nous faudra-t-il pour être content

    S'il y avait la moindre chance que la proposition passe, je proposerais bien de régler une bonne fois pour toute le problème de comportement indéfini en disant que si un dépassement de valeur est remarquable à la compilation, cela doit obligatoirement provoquer une erreur, et que tout dépassement de valeur à l'exécution doit faire pareil.

    Mais une telle proposition sera recue à coups de lancers de pierres!!!!
    Il y a juste un truc qui me choque, c'est le comportement de std::integral_constant<char, 999999> qui compile sur msvc et icc. J'ai bien plus l'habitude de voir une erreur. C'est plutôt sur ce point qu'il faudrait expliciter les règles (ce qui corrigerait le problème de index_impl de la proposition). Il y a le même problème avec if constexpr (2) que tout le monde accepte, sauf clang.
    C'est justement ce genre de chose que je veux régler. Mais, a priori, ce ne pourra être fait qu'au niveau des constexpr, qui sont malgré tout "relativement" récentes et en évolution constante depuis qu'elles sont arrivées.

    Le fait de les rendre purement contextuelles (ce qu'elles sont déjà en pratique, d'après le standard) est à mon sens la solution la plus simple à mettre en oeuvre.

    Je ne prétend pas avoir couvert tous les aspects à prendre en compte (le mot constexpr apparait déjà plus de 999 fois dans la norme, et on peut présumer que ce sont surtout les endroits où il n'apparait pas qu'il faudrait en parler ).

    Mais si on ne propose pas un début de solution à ce niveau là, on peut être sur que les choses n'évolueront jamais
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #12
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Alors je n'ai pas tout comprit dans la proposition. Pour moi, cette proposition vise 3 axes:

    - les promotions
    - les conversions
    - les dépassements en dehors des 2 cas précédents.

    Les promotions sont parfaitement définies par la norme, n'importe quelle opération donne un type int ou rang supérieur. À mon sens, l'overflow de constexpr auto i = std::numeric_limit<uint8_t>::min()-1; n'en est pas un: i est un int de valeur -1. Un overflow impose forcément que le type soit uint8_t, ce qui modifie les règles de promotion, complexifier davantage le langage et le rend incohérent.

    Viens les conversions et les dépassements sur lesquels il existe déjà un certain nombre de règle. Un UB => pas constexpr. Un 99999*99999 dépasse la valeur d'un int et ne devrait pas compiler. Sauf qu'il s'avère que tous les compilateurs ne sont pas d'accord... Et sur ce point, je suis soit pour un durcissement des règles ou une clarification, soit pour une correction des compilateurs fautifs si c'est un bug. Finalement, il n'y a que les non signés qui tronquent silencieusement les valeurs, mais là aussi c'est défini. Pour les conversions implicites dans les templates, je serais pour interdire les overflows (ce qui existe déjà sur clang et gcc), mais pas pour les constexpr en dehors de l'initialisation par accolade (cas actuel).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    constexpr unsigned char a = 256; // pas d'erreur
    constexpr unsigned char b {256}; // erreur
    Combien de types primitifs nous faudra-t-il pour être content ?
    À vrai dire, je ne pense pas à des types primitifs mais des classes qui autorisent les dépassements ou non, autorisent les promotions ou non, permettent de définir des valeurs limites, adaptent le type de sortie pour pouvoir stoker le résultat (int+int = long par exemple, mais il faut des paramètres constexpr), etc, etc.

  13. #13
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Alors je n'ai pas tout comprit dans la proposition. Pour moi, cette proposition vise 3 axes:

    - les promotions
    - les conversions
    - les dépassements en dehors des 2 cas précédents.
    Ma proposition vise, en sommes, à dire qu'il ne peut y avoir de promotion ou de conversion -- lorsque l'on évalue une constexpr -- que si le besoin en est explicite (*) et que, a partir de là, tout dépassement (dans le cadre de l'évaluation d'une constexpr) -- dans un sens ou dans l'autre -- ne peut produire qu'une seule réaction sensée: l'échec de la compilation.

    (*) pour donner un exemple: static auto constexpr x = UINT_MAX +1; n'a aucune raison de provoquer une promotion, vu que le type de x est défini par le type de UINT_MAX .
    Les promotions sont parfaitement définies par la norme, n'importe quelle opération donne un type int ou rang supérieur. À mon sens, l'overflow de constexpr auto i = std::numeric_limit<uint8_t>::min()-1; n'en est pas un: i est un int de valeur -1.
    Le problème ne vient pas de la promotion en elle-même, mais bien du fait que /*U*/CHAR_MAX + 1, /*U*/SHORT_MAX+1,/*U*/LONG_MAX +1 et /*U*/LLONG_MAX +1 (et leurs équivalent /*U*/X_MIN -1) ont, tous, un comportement indéfini, alors que le comportement de /*U*/INT_MAX +1 et de /*U*/INT_MIN -1 est clairement défini comme donnant 0.

    Et le problème, c'est que cette capacité à "absorber" un overflow est "sensée" au runtime (je n'essaye d'ailleurs pas de la supprimer, bien qu'il serait intéressant pour la norme d'homogénéiser un peu tout cela), mais complètement aberrante lors de la compilation, car elle peut provoquer des catastrophes, en permettant à deux éléments différents d'être identifiés (à la compilation) par un indice identique.

    Je propose donc d'échanger deux comportements définis pouvant provoquer des problèmes et huit comportements indéfinis par un seul comportement clairement définis (dans une situation donnée) qui obligera -- au pire -- le développeur à repenser au choix qu'il a fait concernant un type de donnée.

    Ce n'est pas la panacée, j'en suis bien convaincu, mais cela résoudra et évitera déjà pas mal de problèmes

    Si j'avais la moindre de chance de faire accepter par tous qu'un dépassement de valeur est une erreur qui doit provoquer le plantage de l'application lors de l'exécution, crois moi bien, que c'est ce que je proposerais, mais je me rend bien compte que, malheureusement, la base de code existante qui profite justement de ces comportements non définis est bien trop importante pour que je puisse me le permettre
    Un overflow impose forcément que le type soit uint8_t, ce qui modifie les règles de promotion, complexifier davantage le langage et le rend incohérent.
    Ben non, justement... On part de dix comportements (dont 8 sont indéfinis) dans deux situations différentes et on se retrouve avec une situation dans laquelle on a un seul comportement clairement défini et une situation inchangée.

    On gagne la cohérence dans au moins l'une des situations de départ, et cela ne rend pas forcément le langage plus complexe. Encore une fois, si j'avai la moindre chance de faire passer le changement dans un contexte runtime, j'essayerais de le faire passer, ce qui simplifierait encore d'autant le langage

    Viens les conversions et les dépassements sur lesquels il existe déjà un certain nombre de règle. Un UB => pas constexpr. Un 99999*99999 dépasse la valeur d'un int et ne devrait pas compiler. Sauf qu'il s'avère que tous les compilateurs ne sont pas d'accord... Et sur ce point, je suis soit pour un durcissement des règles ou une clarification, soit pour une correction des compilateurs fautifs si c'est un bug.
    Ben justement: il faut commencer par durcir la règle, si l'on veut pouvoir mettre les compilateurs d'accord et les forcer à se corriger

    Le fait de dire que "à la compilation, aucun dépassement de valeur n'est autorisé" (ce qui est, en gros, l'âme même de ma proposition) permet de faire le premier pas, et de définir comme bug tout comportement n'allant pas dans ce sens dans les compilateurs.
    Finalement, il n'y a que les non signés qui tronquent silencieusement les valeurs, mais là aussi c'est défini. Pour les conversions implicites dans les templates, je serais pour interdire les overflows (ce qui existe déjà sur clang et gcc), mais pas pour les constexpr en dehors de l'initialisation par accolade (cas actuel).
    Non, il est cohérent de l'interdire dans tous les cas...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    constexpr unsigned char a = 256; // pas d'erreur
    Comment veux tu justifier que tu arrive à représenter une valeur sensée nécessiter 9 bits pour sa représentation dans un espace mémoire qui n'en contient que 8

    Quelque soit la taille de ton espace mémoire, quelle que soit la signification du bit qui peut servir de "bit de signe", tu ne sais pas louer 11 chambres lorsque tu n'en a que dix à ta disposition
    constexpr unsigned char b {256}; // erreur
    [/c]
    Si tu accepte l'idéé que l'utilisation d'accolades doit provoquer une erreur lors d'un dépassement (ce qui est déjà un bon point ), qu'est-ce qui est le plus facile, selon toi. Est-ce de se dire
    1. qu'un dépassement, quelle que soit son origine provoquera toujours une erreur de compilation ou
    2. qu'un dépassement peut provoquer une erreur de compilation... ou non, en fonction de la manière dont la valeur est obtenue

    N'est il pas moins complexe et plus cohérent de considérer (1) comme la meilleure solution, même si cela implique de devoir l'exprimer correctement

    À vrai dire, je ne pense pas à des types primitifs mais des classes qui autorisent les dépassements ou non, autorisent les promotions ou non, permettent de définir des valeurs limites, adaptent le type de sortie pour pouvoir stoker le résultat (int+int = long par exemple, mais il faut des paramètres constexpr), etc, etc.
    Cela ne ferait aucune différence, si ce n'est que nous parlerions alors de type sous-jacent, vu qu'il faudra bien disposer d'un moyen de représenter la valeur associée aux différentes instance de ces classes
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. [AJAX] Tester les champs d'un formulaire avant de pouvoir l'envoyer
    Par italiasky dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 02/05/2007, 12h25
  2. écrire dans un xml avant d'envoyer au serveur
    Par eloifi dans le forum Struts 1
    Réponses: 5
    Dernier message: 23/10/2006, 17h16
  3. [Upload] Renommer un fichier avant de l'envoyer
    Par wishmastah dans le forum Langage
    Réponses: 10
    Dernier message: 02/04/2006, 01h25

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