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 :

templates: tuple de foncteurs et operateurs ()


Sujet :

Langage C++

  1. #1
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut templates: tuple de foncteurs et operateurs ()
    Depuis plusieurs mois, je travaille sur une bibliothèque de composition de fonctions mathématiques et plus si affinités.

    Et finalement, je rencontre un problème de construction template.

    Le besoin de base est le suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template <des choses, typename F...>
    struct tied_functors {
    std::tuple<F...> functors;
     
    using signature = std::tuple < std::common_type<le I° argument de chaque F>... >;
     
    auto operator() (Args...) const {
        static_assert( Args... compatible avec signature partagée, "appel impossible" );
        return std::make_tuple( functors[](Args...)... );
    }
    };
    Je passe sous le capot la partie qui permet d'accéder à tous les foncteurs (ca implique std::get<> et std::integer_sequence)

    Mon soucis concret, c'est qu'actuellement, ma détection de signature ne fonctionne pas avec les références (non const).

    Or common_type est défini pour retirer les références (et cv-qualifiers)
    du coup, si j'ai deux foncteurs de type iterator double(Bidule&) et int(Bidule&), j'ai bien comme signature effective std::tuple<double, int>(Bidule).

    Ce qui ne fonctionne pas, car le bidule transmis en référence non constante est une temporaire.

    Ma question s'articule donc ainsi:
    sachant deux foncteurs f(A) et g(B), quel type C d'argument dois-je utiliser pour le transmettre à f et g?

    Je n'arrive pas à me dépatouiller dans la norme…
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  2. #2
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Salut,

    je sais pas si c'est directement utile ou pas, mais ça m'a rappelé cette présentation de Joel et Edouard (il me semble) lors d'un meetup y'a ~2ans : https://github.com/edouarda/brigand/wiki
    Peut-être que tu y trouveras ton bonheur ?
    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 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
    Avec brigand on peut faire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    template<class T, class U>
    using ternary_conditional_t = decltype(1 ? std::declval<T>() : std::declval<U>());
     
    template<class T, class... Ts>
    using common_type = brigand::fold<
      brigand::list<Ts...>,
      T,
      brigand::bind<ternary_conditional_t, brigand::_1, brigand::_2>
    >;
    Mais pour garder une référence, il faut que tous les types soient identiques.

  4. #4
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Ca pourra m'être utile, en effet, mais ca n'est pas mon blocage du moment.

    je n'arrive pas à trouver tous les cas valides, et ceux qui ne le sont pas, pour lier deux foncteurs (ou plus, d'ailleurs).

    Si je résume mon problème en supprimant la couche variadique, et en limitant mes foncteurs à un argument.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <typename R1, typename A1, typename R2, typename A2>
    struct unary_functor_pair {
        using F1 = std::function<R1(A1)>;
        using F2 = std::function<R2(A2)>;
     
        using arg_type = ???
     
        auto operator() (arg_type arg) { return std::make_tuple( f1(arg), f2(arg) ); }
     
    private:
        F1 f1;
        F2 f2;
    };
    note: mon code réel utilise directement le type réel de F1 et F2, pas une std::function, mais ca ne change pas le problème présent

    pour A1 = T &, A2=T const&, il faut nécessairement que arg_type soit T &, car ca ne peut pas être un T ou un T const&.
    pour [c]T[c] et T const&, j'ai le choix entre T et T const&, et ce second choix me semble meilleur.

    mais pour toutes les combinaisons de {/?/&&} et {/const/const volatile/volatile}, je ne sais pas vraiment.
    Et vient aussi la question de l'utilisation de std::forward.

    plus traitre, que faire pour Vehicule&, Camion const&. A priori, Camion&Edit: croisé avec ta réponse, jo_link_noir.
    Ca m'a l'air une bonne piste, mais ca marchera aussi avec l'héritage?
    Et les conversions simples, genre int const&, long const&.
    J'ai du mal à voir ce qui est sensé être valable de ce qui est une fausse idée que je me fais.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  5. #5
    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
    As-tu vraiment besoin de arg_type/signature ? Parce qu'il est plus simple de vérifier si un type est valide pour un appel de fonction (std::is_callable) que de trouver le type commun. D'ailleurs, il peut ne pas exister de type commun déductible (paramètre class C : A,B{} avec f(A) et g(B). C est bon, mais aucun commun entre A et B).

    Ca m'a l'air une bonne piste, mais ca marchera aussi avec l'héritage?
    Et les conversions simples, genre int const&, long const&.
    Non, le type est différent alors (grosso modo) le compilateur fait un decay<T>/decay<U> selon qui est convertible en quoi.

  6. #6
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Hum, en effet, c'est une judicieuse remarque. Par contre, std::is_callable est disponible en C++17 uniquement. à voir si je peux le reproduire en C++14, voire 11.

    Donc, je vais voir réviser mon système de restriction.
    De même, si une référence constante et référence non constante peuvent toutes deux être alimentée par une référence modifiante, l'ordre des opérations n'aura pas forcément un effet stable.

    Je vais continuer à spécifier mes intentions.

    Ou alors, je cherche fabrique un algo du genre invoke/apply et je le compose sur un tuple de functeur.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  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
    Salut,
    Citation Envoyé par ternel Voir le message
    Hum, en effet, c'est une judicieuse remarque. Par contre, std::is_callable est disponible en C++17 uniquement. à voir si je peux le reproduire en C++14, voire 11.

    Donc, je vais voir réviser mon système de restriction.
    De même, si une référence constante et référence non constante peuvent toutes deux être alimentée par une référence modifiante, l'ordre des opérations n'aura pas forcément un effet stable.

    Je vais continuer à spécifier mes intentions.

    Ou alors, je cherche fabrique un algo du genre invoke/apply et je le compose sur un tuple de functeur.
    A défaut, tu pourrais peut être envisager de tester la présence de l'opérateur () à l'aide de std::is_member_function_pointer, vu que, l'un dans l'autre, tu travailleras essentiellement avec des foncteurs... Non

    (je me demande d'ailleurs si std::is_callable ne fera pas purement et simplement appel à std::is_member_function_pointer en interne )
    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

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    a priori, is_callable devrait faire appel à is_convertible et result_of.

    Pour l'instant, j'ai regardé dans la STL de mon brave g++ 6.2. Et je n'y ai rien trouvé.

    Je vais prendre le temps d'écrire scrupuleusement ce que je veux, de peser le pour et le contre, et revoir toute ma soupe.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  9. #9
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Pour référence, j'ai trouvé une implémentation C++11 de is_callable, qui contourne la restriction de std::result_of qui fut levée au passage à C++14.

    Lisez l'article complet pour plus d'informations.

    En résumé, la norme parle d'une pseudo macro "INVOKE", et is_callable sert à tester si l'utilisation serait valide.
    Ca fera un bon point de départ pour mon problème. Quand j'aurai du temps sur un compilateur assez moderne.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  10. #10
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Après avoir réfléchi, je m'aperçoit que je suis dans une sorte d'impasse.

    J'essaie de valider à la compilation de l'expression si elle peut être valide, puis à son exécution qu'elle l'est effectivement.

    Par exemple, les deux fonctions int f(int) et int g(int, int) sont incompatible car elles ne peuvent pas être appelée sur les mêmes arguments.
    Ca, je dois le détecter par leurs aritées.

    Mais je souhaite aussi accepter des foncteurs génériques (donc templates), comme std::plus<void>() ou [](auto a) {return a*a;}.
    Dans ces cas là, je ne peux même pas calculer l'arité, donc faire un quelconque controle avant l'appel.

    La "solution" est simple: soit je laisse tomber la vérification systématique à la construction, soit j'abandonne le support des foncteurs génériques.
    En pratique, je vais probablement faire les deux, pour deux versions différentes de la bibliothèque, puisque c'est un projet personnel et que j'ai le temps.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

Discussions similaires

  1. template et heritage de l'operateur =
    Par yan dans le forum Langage
    Réponses: 2
    Dernier message: 25/02/2011, 09h22
  2. Réponses: 6
    Dernier message: 12/07/2007, 18h18
  3. Foncteur, classes templates et héritage
    Par Floréal dans le forum C++
    Réponses: 8
    Dernier message: 17/06/2007, 21h56
  4. [Template] Surcharge operateur
    Par juls64 dans le forum C++
    Réponses: 7
    Dernier message: 04/05/2007, 19h35
  5. [Débutant]Foncteur et operator() template
    Par Sub dans le forum Langage
    Réponses: 14
    Dernier message: 27/03/2007, 13h51

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