Journal Toujours plus proche du Python avec C++

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
20
22
déc.
2020

Demat'iNal,

Le langage Python offre une sacré flexibilité dans la gestion des arguments: arguments positionnels, nommés, passage par mot-clef, c'est un peu la foire à la saucisse des paramètres.

J'aime particulièrement le passage d'arguments nommés, ça documente bien le site d'appel, et la syntaxe Python est assez flexible. Mais peut-on en faire autant avec C++ ? Tu te doutes bien que oui, sinon l'intérêt de ce journal serait limité :-)

La syntaxe supportée :

auto res = my_function_call(pos0, pos1,
                            "keyword0"_kw=value0,
                            "keyword1"_kw=value1)

Et au niveau de la fonction appelée, ça ressemble à

template<typename... KWArgs>
auto my_function_call(int pos0, int pos1, KWArgs&&... kwargs) {
    auto Args = params14::parse(std::forward<KWArgs>(kwargs)...);
    auto kw0 = Args.get("keyword0"_kw);
    auto kw2 = Args.get("keyword2"_kw, nullptr);
    ...
}

Plus de précision sur cette belle magie dans le README du dépôt

https://github.com/serge-sans-paille/params14

La code est sous licence Apache-2.0, c'est un petit fichier d'en-tête de moins de 200 lignes de C++14, commentaires compris ;-)

l'API gagnerait certainement à être un peu étoffée, des idées ?

Nedeleg laouen d’hol lennerien!

  • # Littéraux non-standard

    Posté par  . Évalué à 2.

    Il me semble que la syntaxe des littéraux que tu utilises pour _kw est une extension GNU et pas du C++14 standard.

    Je me souviens avoir essayé de créer des littéraux du même genre (chaîne dans un type) et de ne rien avoir trouvé de pratique et standard. Peut-être que C++20 améliore ça, je ne connais pas encore très bien.

    • [^] # Re: Littéraux non-standard

      Posté par  (site web personnel) . Évalué à 4.

      Wé c'est pas standard, mais supporté par gcc et clang :-/
      J'ai pas trouvé comment avoir une approche similaire sans ça…

      • [^] # Re: Littéraux non-standard

        Posté par  . Évalué à 4.

        J'ai tenté un bricolage en C++20 : https://gcc.godbolt.org/z/MxExbc

        Ça semble possible avec la nouvelle syntaxe mais je ne garantie pas le qualité de ce que j'ai écrit. Ça marche avec GCC et clang, mais MSVC ne semble pas encore supporter la nouvelle syntaxe. Donc en pratique, pour l'instant, c'est pas mieux que l'extension GNU mais ça pourrait être le futur.

  • # Moui

    Posté par  (site web personnel) . Évalué à 5.

    J'aime bien l'idée mais j'y vois pas mal d'inconvénients :)

    • Je plussois sur l'intérêt de documenter le site d'appel, mais en l'état on n'a plus les noms ni les types des paramètres dans la signature de l'appelé.
    • Second problème, l'appelé est forcément template. Ais-je envie d'augmenter mes temps de compilation pour pouvoir nommer les paramètres côté appelant ? Probablement non.
    • Comment passer des paramètres par adresse ? Combien de copies des paramètres sont faites entre l'appelant et l'appelé ? Sur cet exemple j'ai déjà une copie superflue :
    #include "../include/params14.hpp"
    
    #include <cstdio>
    
    using namespace params14::literals;
    
    int copy_count = 0;
    int assign_count = 0;
    
    struct copy_counter
    {
      int value;
    
      copy_counter(int v) : value(v) {}
      copy_counter(const copy_counter& that)
        : value(that.value)
      {
        ++copy_count;
      }
    
      copy_counter& operator=(const copy_counter& that)
      {
        value = that.value;
        ++assign_count;
        return *this;
      }
    };
    
    template<typename... KWArgs>
    void foo(KWArgs&&... kwargs)
    {
      auto args = params14::parse(std::forward<KWArgs>(kwargs)...);
      printf("value=%d\n", args.get("p"_kw).value);
    }
    
    void bar(const copy_counter& p)
    {
      printf("value=%d\n", p.value);
    }
    
    int main(int argc, char** argv)
    {
      copy_counter p(argc);
      foo("p"_kw=p);
      printf("named:\ncopies=%d\nassignments=%d\n", copy_count, assign_count);
    
      copy_count = 0;
      assign_count = 0;
      bar(p);
      printf(
             "good old syntax:\ncopies=%d\nassignments=%d\n",
             copy_count, assign_count);
    
      return 0;
    }
    • [^] # Re: Moui

      Posté par  . Évalué à 2.

      Comment passer des paramètres par adresse ?

      J'imagine que reference_wrapper doit passer.

      foo("p"_kw=std::ref(p));
    • [^] # Re: Moui

      Posté par  (site web personnel) . Évalué à 5.

      mais en l'état on n'a plus les noms ni les types des paramètres dans la signature de l'appelé.

      C'est vrai. Mais on l'a dans le corps de la fonction, surtout si on se passe de auto

      int & val = args.get("some_integer_ref"_kw);

      l'appelé est forcément template.

      Voui :-) ensuite on peut imaginer que cet appelé face un appel à une fonction non-template une fois les arguments traités.

      Comment passer des paramètres par adresse ? Combien de copies des paramètres sont faites entre l'appelant et l'appelé ? Sur cet exemple j'ai déjà une copie superflue

      Merci pour ce cas test, ce bug est corrigé (et tu es crédité dans le commit !), plus de copie maintenant. Pour le passage par référence, c'est les règles de déductions des universal références classiques.

      • [^] # Re: Moui

        Posté par  . Évalué à 4.

        Voui :-) ensuite on peut imaginer que cet appelé face un appel à une fonction non-template une fois les arguments traités.

        vu le nombre de commentaires concernant l'obfuscation, on pourrait imaginer une macro qui s'occupe d'enrober une signature « simple » pour générer la version avec template et arguments nommés.

        C'est pas beau les macros, mais bon, hein, si les gens le demandent…

  • # Limité

    Posté par  . Évalué à 3.

    Tu documente le site appelant, mais la fonction elle-même y perd…

    • dans ton exemple je ne sais pas dire quels sont les types valides de keyword?
    • la définition des paramètres n'est pas déclaratives ce qui va complexifier de beaucoup l'analyse statique
    • ça génère quoi comme erreur en cas d'erreur ? (mauvais type, mauvais nom,…)

    Ça me parait plus apporter de complexité que de solution.

    https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Limité

      Posté par  (site web personnel) . Évalué à 7.

      dans ton exemple je ne sais pas dire quels sont les types valides de keyword?

      Dans mon exemple, j'ai semé des auto partout pour rester pythonic. Mais tu peux aussi écrire

      float & f = args.get("keyword"_kw);

      si tu veux forcer le type (et/ou le documenter).

      la définition des paramètres n'est pas déclaratives

      Carrément.

      ça génère quoi comme erreur en cas d'erreur ? (mauvais type, mauvais nom,…)

      Y a des static_assert, donc tu as un message à la compil, mais moins élaboré / lisible que ce que peut produire un compilo, c'est sûr hein :-)

      Ça me parait plus apporter de complexité que de solution

      C'est surtout pour la beauté de la chose, pour la note artistique, le frisson de repousser les limites du langage :-)

  • # Autres pistes

    Posté par  . Évalué à 5.

    Boost propose également quelque-chose du même genre. Faut juste… réussir à comprendre la doc de Boost.

    Jonathan Boccara avait également publié un article sur le sujet.

    J'apprécie également cette approche, qui a le mérite de rester très simple.

    Il y a également eu la Proposal N4172: Named arguments, mais qui semble être tombée à l'eau.

    • [^] # Re: Autres pistes

      Posté par  (site web personnel) . Évalué à 5.

      Chouette biblio ! Peu de temps après avoir posté ce journal, on m'a également pointé https://github.com/jfalcou/raberu qui est en C++20 mais reprend des idées des différentes approches que tu cites :-)

      On notera que l'article de Jonathan Boccara que tu mentionnes répond à certaines des question de ce fil ;-)

  • # Une autre idée d'implémentation des paramètres nommés en C++

    Posté par  . Évalué à 1. Dernière modification le 27 décembre 2020 à 23:57.

    Bon c'est sympa, mais faire du refactoring avec des arguments nommés via du texte c'est pas facile. Du coup j'ai pensé à faire autrement: https://godbolt.org/z/xfEMGc

    C'est pas compatible C++14 à cause de if constexpr, on doit pouvoir faire sans, mais j'ai jamais vraiment réussi à faire des spécialisations de templates. Par contre ça fonctionne aussi sur MSVC! Pareil c'est pas adapté pour les environements sans new/delete, mais j'avais la flemme de reprendre ton idée d'utiliser un tuple.

    Voilà bonne soirée :)

Suivre le flux des commentaires

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