Partage
  • Partager sur Facebook
  • Partager sur Twitter

Mauvais std::tuple retourné par la fonction.

Sfinae ne fonctionne pas.

Sujet résolu
    12 juin 2021 à 0:20:32

    Salut! Histoire de m'exercer un peu avec les template variadiques et les tuples, je voudrais supprimer tout les types qui ne sont pas des placeholders d'un tuple.

    J'ai donc créer les structures suivantes :

    template <size_t I, typename T>
    struct placeholder {
    
    };
    template<class T>
    struct is_placeholder
    : std::false_type
    {};
    
    template<std::size_t I, class T>
    struct is_placeholder<placeholder<I, T>>
    : std::true_type
    {};
    struct LessPlaceceholder
    {
      template<class PlaceHolder1, class PlaceHolder2>
      using f = std::bool_constant<PlaceHolder1::index < PlaceHolder2::index>;
    };
    //Make index sequences from an offset.
    template<std::size_t N, typename Seq> struct offset_sequence;
    
    template<std::size_t N, std::size_t... Ints>
    struct offset_sequence<N, std::index_sequence<Ints...>>
    {
     using type = std::index_sequence<Ints + N...>;
    };
    template<std::size_t N, typename Seq>
    using offset_sequence_t = typename offset_sequence<N, Seq>::type;
    template <size_t O, size_t N, size_t... Ints>
    std::index_sequence<Ints...> make_offset_sequence () {
        return offset_sequence_t<O, std::make_index_sequence<N>>::type();
    }
    //Concatenate two sequences of indexes into one.
    template<typename Seq1, typename Seq> struct cat_sequence;
    template<std::size_t... Ints1, std::size_t... Ints2>
    struct cat_sequence<std::index_sequence<Ints1...>,
                        std::index_sequence<Ints2...>>
    {
     using type = std::index_sequence<Ints1..., Ints2...>;
    };
    template<typename Seq1, typename Seq2>
    using cat_sequence_t = typename cat_sequence<Seq1, Seq2>::type;
    template<typename T, std::size_t... Ints>
    //Return a tuple with elements at indexes.
    auto select_tuple(std::index_sequence<Ints...>)
    {
     T tuple;
     return std::make_tuple(
        std::get<Ints>(std::forward<T>(tuple))...);
    }
    //Remove the Nth elements of a tuple.
    template<std::size_t N, typename T>
    auto remove_Nth()
    {
      constexpr auto size = std::tuple_size_v<T>;
      using first = std::make_index_sequence<N>;
      using rest = offset_sequence_t<N+1,
                    std::make_index_sequence<size-N-1>>;
      using indices = cat_sequence_t<first, rest>;
      return select_tuple<T>(indices{});
    }
    //Remove every types, that are not satisfied a condition.
    template<template<class> class Pred, template <class...> class R>
    struct append_if {
        //If the type statisfy the condition we don't remove it and return the tuple itself.
        template <size_t I, class B, class T, class... D, class = typename std::enable_if<B::value>::type>
        static constexpr auto get(T tp) {
            return tp;
        }
        //If the type doesn't satisfy the condition we remove it from the tuple and return the new tuple.
        //As a good surprise for me it works event if the type of the returned tuple is not the same as the type of the provided tuple.
        template <size_t I, class B, class T, class = typename std::enable_if<!B::value>::type>
        static constexpr auto get(T tp) {
            //std::cout<<"I : "<<I<<std::endl;
            return remove_Nth<I, T>();
        }
        //final case : we stop recursion when we have browsed all the elements of the tuple and we check if the last element must be removed.
        template <size_t I=0, size_t NB=0, class T, class... D, class = typename std::enable_if<(I-NB == std::tuple_size<T>()-1 && Pred<decltype(std::get<I-NB>(T()))>::value)>::type>
        static constexpr auto f(T tp) {
            return get<I-NB, std::true_type>(tp);
        }
        template <size_t I=0, size_t NB=0, class T, class... D, class... E, class = typename std::enable_if<(I-NB == std::tuple_size<T>()-1 && !Pred<decltype(std::get<I-NB>(T()))>::value)>::type>
        static constexpr auto f(T tp) {
            return get<I-NB, std::false_type>(tp);
        }
        template <size_t I=0, size_t NB=0, class T, class... D, class... E, class... F, class = typename std::enable_if<(I-NB < std::tuple_size<T>()-1  && Pred<decltype(std::get<I-NB>(T()))>::value)>::type>
        static constexpr auto f(T tp) {
            return f<I+1, NB>(get<I-NB, std::true_type>(tp));
        }
        template <size_t I=0, size_t NB=0, class T, class... D, class... E, class... F, class... G, class = typename std::enable_if<(I-NB < std::tuple_size<T>()-1  && !Pred<decltype(std::get<I-NB>(T()))>::value)>::type>
        static constexpr auto f(T tp) {
            return f<I+1, NB+1>(get<I-NB, std::false_type>(tp));
        }
    };
    //Copy every tuple elements which satisfied a predicate.
    template<template <class> class Pred, typename T, template <class> class R>
    struct copy_if {
        using f = decltype(append_if<Pred, R>::template f(T()));
    };
    
    int main(int argc, char* argv[])
    {
        using late_params_t
                  = copy_if<is_placeholder,std::tuple<placeholder<3, int>, float, placeholder<2, int>, int, placeholder<1, int>, char, placeholder<0, int>>, std::tuple>::f;
        std::tuple<placeholder<3, int>, float, placeholder<2, int>, int, placeholder<1, int>, char, placeholder<0, int>> tp;
        auto rtp = remove_Nth<1, decltype(tp)>();
        auto rtp2 = remove_Nth<2, decltype(rtp)>();
        auto rtp3 = remove_Nth<3, decltype(rtp2)>();
        std::cout<<typeid(late_params_t).name()<<std::endl;
        late_params_t t = std::make_tuple(placeholder<3, int>(), placeholder<2, int>(), placeholder<1, int>(), placeholder<0, int>());
        return 0;    
    }



    Le problème est que le tuple retourné n'est pas bon et je ne comprends pas pourquoi.

    Normalement il devrait faire ceci :

    Première itération, I vaut 0, c'est un placeholder donc il sélectionne cette fonction ci :

    template <size_t I=0, size_t NB=0, class T, class... D, class... E, class... F, class = typename std::enable_if<(I != std::tuple_size<T>()-1 && Pred<decltype(std::get<I>(T()))>::value)>::type>

    Il doit appeler la version de get qui renvoie le tuple et rappelle la fonction F avec I = 1, NB = 0 et le tuple inchangé : 

    std::tuple<placeholder<3, int>, float, placeholder<2, int>, int, placeholder<1, int>, char, placeholder<0, int>>

    Le deuxième élément n'est pas un placeholder donc il doit appeler la version de get qui le supprime (en I = 1) et le passer à la fonction ou I=2, NB=1 et le nouveau tuple est :

    std::tuple<placeholder<3, int>, placeholder<2, int>, int, placeholder<1, int>, char, placeholder<0, int>>

    L'élément I-NB (2-1 = 1) est un placeholder, le tuple reste inchangé donc et vaut : 

    std::tuple<placeholder<3, int>, placeholder<2, int>, int, placeholder<1, int>, char, placeholder<0, int>>

    Ensuite I vaut 3 et NB vaut 1, I-NB vaut 3-1 = 2 et le troisième élément (pas le quatrième parce que comme un élément a été supprimé les éléments du tuple ont été décalés) n'est pas un placeholder donc il le supprime, NB vaut 2, I vaut 4 et le nouveau tuple est :

    placeholder<3, int>, placeholder<2, int>, placeholder<1, int>, char, placeholder<0, int>>
    Et ainsi de suite jusqu'à que l'on arrive au dernière élément c'est à dire, le nombres d'élements - le nombre d'élements supprimés.
    Donc je devrais avoir le tuple suivant : std::tuple<placeholder<3, int>, placeholder<2, int>, placeholder<1, int>, placeholder<0, int>>
    Mais ça ne me donne pas le tuple auxquels je m'attends, ça me donne ce tuple-ci!!! (Il me supprime tout!!!) :
    tuple<>
    Ce qui est totalement faux!!! (Du coup erreur en compilation parce que les types ne sont pas bon!!!
    Je ne trouve pas ça normal, bref, je ne comprend pas ou est mon erreur. 
    EDIT : SFFinae me sélectionne la mauvaise fonction déjà lorsque je test avec un seul élément (un seul placeholder) il me le supprime hors qu'il ne devrait pas...

    -
    Edité par OmbreNoire 12 juin 2021 à 1:31:35

    • Partager sur Facebook
    • Partager sur Twitter
      12 juin 2021 à 2:45:36

      Ton code est bien trop compliqué. Pour faire une boucle, soit on incrémente un indice, soit on enlève un élément d'une variadique à chaque récursion. Et pour accumuler les éléments, soit on les ajoute dans une variadique, soit dans un contexte qui se manipule avec de la spécification de template.

      Par contre, c'est impossible d'avoir plusieurs variadiques si celle-ci ne sont pas attachées à un contexte englobant.

      template<class... Ts, class... Us> // quoiqu'on fasse, Us sera toujours vide
      void foo();
      template<class... Ts, class... Us>
      void foo(list<ts...>, Us...); // ok

      Et si on ne veut pas de paramètre dans la fonction, la spécialisation de template sera obligatoire.

      Pour info decltype(std::get<I>(T())) revient à faire std::tuple_element<I, T> et depuis C++14, les traits disposent d'une version avec _t comme suffixe: typename std::enable_if<....>::type == std::enable_if_t<....>

      -
      Edité par jo_link_noir 12 juin 2021 à 2:47:50

      • Partager sur Facebook
      • Partager sur Twitter
        12 juin 2021 à 8:15:29

        jo_link_noir a écrit:

        Ton code est bien trop compliqué. Pour faire une boucle, soit on incrémente un indice, soit on enlève un élément d'une variadique à chaque récursion. Et pour accumuler les éléments, soit on les ajoute dans une variadique, soit dans un contexte qui se manipule avec de la spécification de template.

        Par contre, c'est impossible d'avoir plusieurs variadiques si celle-ci ne sont pas attachées à un contexte englobant.

        template<class... Ts, class... Us> // quoiqu'on fasse, Us sera toujours vide
        void foo();
        template<class... Ts, class... Us>
        void foo(list<ts...>, Us...); // ok

        Et si on ne veut pas de paramètre dans la fonction, la spécialisation de template sera obligatoire.

        Pour info decltype(std::get<I>(T())) revient à faire std::tuple_element<I, T> et depuis C++14, les traits disposent d'une version avec _t comme suffixe: typename std::enable_if<....>::type == std::enable_if_t<....>

        -
        Edité par jo_link_noir il y a environ 4 heures

        Oui c'est ce que je fais, j'incrémente I à chaque récursion (j'utilise juste un second compteur en plus avec une spécialisation de la fonction pour compter le nombre d'éléments supprimés) et le std::tuple me permet de ne pas avoir à me soucier des types des paramètres variadique pour tout les éléments lors de la récursion parce que de toute façon je ne les connais pas.

        Je n'ai pas plusieurs template variadique dans la fonction f à part ceux que j'utilise pour sfinae pour lever l'ambiguïté lors de l'appel à la fonction f lors de la récursion mais ils sont vides cela me permet de ne pas avoir d'erreur comme quoi il ne peut pas déduire le type lorsque j'utilise un template non variadique, donc, de ce côté là il n'y a pas de problème.

        J'ai remplacé decltype(get<I-NB>(T())) par std::tuple_element<I-NB, T>> mais ça ne résous pas le problème.

        Bref, la récursion fonctionne parce que il me supprime tout les éléments ou alors si j'inverse il ne me supprime aucun élément, ce qui ne fonctionne pas, c'est la structure is_placeholder :

        template <size_t I=0, size_t NB=0, class T, class... D, class = typename std::enable_if<(I-NB == std::tuple_size<T>()-1 && Pred<std::tuple_element<I-NB, T>>::value)>::type>
            static constexpr auto f(T tp) {
                return get<I-NB, std::true_type>(tp);
            }
            template <size_t I=0, size_t NB=0, class T, class... D, class... E, class = typename std::enable_if<(I-NB == std::tuple_size<T>()-1 && !Pred<std::tuple_element<I-NB, T>>::value)>::type>
            static constexpr auto f(T tp) {
                return get<I-NB, std::false_type>(tp);
            }

        Où Pred n'est rien d'autre que is_placeholder qui apparemment vaut toujours false!!! (Puisqu'il me supprime tout les éléments ou aucun si je veux supprimer les placeholders)

        Où alors c'est sfinae qui ne fonctionne pas et il me choisit toujours la même fonction, et je ne vois pas comment faire ça avec la spécialisation de template de classe parce que déjà on ne peut pas utiliser std::enable_if dans la spécialisation de template d'une structure. (Sinon je me débarasserai bien de sfinae qui ne fonctionne pas)

        Je ne comprend pas pourquoi il m'appelle toujours la même version de la fonction f à la compilation.

        Pourtant j'utilise sfinae dans mon système de sérialisation et là ça fonctionne sans aucun problème!!!

        • Partager sur Facebook
        • Partager sur Twitter
          12 juin 2021 à 10:42:47

          tuple_element_t

          Ça ne sert à rien d'avoir des variadiques qui seront toujours vides pour lever une ambiguïté. S'il y a ambiguïté c'est bien parce qu'il y a 2 choix, modifier un proto pour ajouter des variadiques revient à définir une priorisation de correspondance, donc ouais, plus d'ambiguïté, mais ce sera toujours la même qui est utilisée.

          > je ne vois pas comment faire ça avec la spécialisation de template de classe parce que déjà on ne peut pas utiliser std::enable_if dans la spécialisation de template d'une structure

          Si, le standard a même introduit std::void_t par ce que c'est une utilisation fréquente dans ce cas d'usage.

          Mais en réalité, il n'y a absolument pas besoin de enable_if, Pred<...>::type retourne std::true/false_type, il suffit de le mettre directement dans le paramètre du get.

          • Partager sur Facebook
          • Partager sur Twitter
            12 juin 2021 à 12:57:00

            jo_link_noir a écrit:

            tuple_element_t

            Ça ne sert à rien d'avoir des variadiques qui seront toujours vides pour lever une ambiguïté. S'il y a ambiguïté c'est bien parce qu'il y a 2 choix, modifier un proto pour ajouter des variadiques revient à définir une priorisation de correspondance, donc ouais, plus d'ambiguïté, mais ce sera toujours la même qui est utilisée.

            > je ne vois pas comment faire ça avec la spécialisation de template de classe parce que déjà on ne peut pas utiliser std::enable_if dans la spécialisation de template d'une structure

            Si, le standard a même introduit std::void_t par ce que c'est une utilisation fréquente dans ce cas d'usage.

            Mais en réalité, il n'y a absolument pas besoin de enable_if, Pred<...>::type retourne std::true/false_type, il suffit de le mettre directement dans le paramètre du get.

            Ho ça fonctionne mille merci tu me sauve la vie!!!

            template <size_t I, typename T>
            struct placeholder {
            
            };
            template<class T>
            struct is_placeholder
            : std::false_type
            {};
            
            template<std::size_t I, class T>
            struct is_placeholder<placeholder<I, T>>
            : std::true_type
            {};
            struct LessPlaceceholder
            {
              template<class PlaceHolder1, class PlaceHolder2>
              using f = std::bool_constant<PlaceHolder1::index < PlaceHolder2::index>;
            };
            //Make index sequences from an offset.
            template<std::size_t N, typename Seq> struct offset_sequence;
            
            template<std::size_t N, std::size_t... Ints>
            struct offset_sequence<N, std::index_sequence<Ints...>>
            {
             using type = std::index_sequence<Ints + N...>;
            };
            template<std::size_t N, typename Seq>
            using offset_sequence_t = typename offset_sequence<N, Seq>::type;
            template <size_t O, size_t N, size_t... Ints>
            std::index_sequence<Ints...> make_offset_sequence () {
                return offset_sequence_t<O, std::make_index_sequence<N>>::type();
            }
            //Concatenate two sequences of indexes into one.
            template<typename Seq1, typename Seq> struct cat_sequence;
            template<std::size_t... Ints1, std::size_t... Ints2>
            struct cat_sequence<std::index_sequence<Ints1...>,
                                std::index_sequence<Ints2...>>
            {
             using type = std::index_sequence<Ints1..., Ints2...>;
            };
            template<typename Seq1, typename Seq2>
            using cat_sequence_t = typename cat_sequence<Seq1, Seq2>::type;
            template<typename T, std::size_t... Ints>
            //Return a tuple with elements at indexes.
            auto select_tuple(std::index_sequence<Ints...>)
            {
             T tuple;
             return std::make_tuple(
                std::get<Ints>(std::forward<T>(tuple))...);
            }
            //Remove the Nth elements of a tuple.
            template<std::size_t N, typename T>
            auto remove_Nth()
            {
              constexpr auto size = std::tuple_size_v<T>;
              using first = std::make_index_sequence<N>;
              using rest = offset_sequence_t<N+1,
                            std::make_index_sequence<size-N-1>>;
              using indices = cat_sequence_t<first, rest>;
              return select_tuple<T>(indices{});
            }
            //Remove every types, that are not satisfied a condition.
            template<template<class...> class Pred, template <class...> class R>
            struct append_if {
                //If the type statisfy the condition we don't remove it and return the tuple itself.
                template <size_t I, class B, class T, class = typename std::enable_if<B::value>::type>
                static constexpr auto get(T tp) {
                    return tp;
                }
                //If the type doesn't satisfy the condition we remove it from the tuple and return the new tuple.
                //As a good surprise for me it works event if the type of the returned tuple is not the same as the type of the provided tuple.
                template <size_t I, class B, class T, class... D, class = typename std::enable_if<!B::value>::type>
                static constexpr auto get(T tp) {
                    //std::cout<<"I : "<<I<<std::endl;
                    return remove_Nth<I, T>();
                }
                //final case : we stop recursion when we have browsed all the elements of the tuple and we check if the last element must be removed.
                template <size_t I=0, size_t NB=0, class T, class = typename std::enable_if<(I-NB == std::tuple_size<T>()-1 && Pred<std::tuple_element_t<I-NB, T>>::value)>::type>
                static constexpr auto f(T tp) {
                    return get<I-NB, std::true_type>(tp);
                }
                template <size_t I=0, size_t NB=0, class T, class... D, class = typename std::enable_if<(I-NB == std::tuple_size<T>()-1 && !Pred<std::tuple_element_t<I-NB, T>>::value)>::type>
                static constexpr auto f(T tp) {
                    return get<I-NB, std::false_type>(tp);
                }
                template <size_t I=0, size_t NB=0, class T, class... D, class... E, class = typename std::enable_if<(I-NB < std::tuple_size<T>()-1  && Pred<std::tuple_element_t<I-NB, T>>::value)>::type>
                static constexpr auto f(T tp) {
                    return f<I+1, NB>(get<I-NB, std::true_type>(tp));
                }
                template <size_t I=0, size_t NB=0, class T, class... D, class... E, class ... F, class = typename std::enable_if<(I-NB < std::tuple_size<T>()-1 && !Pred<std::tuple_element_t<I-NB, T>>::value)>::type>
                static constexpr auto f(T tp) {
                    return f<I+1, NB+1>(get<I-NB, std::false_type>(tp));
                }
            };
            //Copy every tuple elements which satisfied a predicate.
            template<template <class...> class Pred, typename T, template <class> class R>
            struct copy_if {
                using f = decltype(append_if<Pred, R>::template f(T()));
            };
            
            int main(int argc, char* argv[])
            {
                using late_params_t
                          = copy_if<is_placeholder,std::tuple<placeholder<3, int>, float, placeholder<2, int>, int, placeholder<1, int>, char, placeholder<0, int>>, std::tuple>::f;
                late_params_t t = std::make_tuple(placeholder<3, int>(),placeholder<2, int>(),placeholder<1, int>(),placeholder<0, int>());
                return 0; }

            Il fallait utiliser std::tuple_element_t! C'est le decltype(std::get<I-NB>(T()) qui ne fonctionnait pas. Mais je suis sûr que je peux encore améliorer ce bout de code en retirant l'instanciation du std::tuple et en gagnant en performance en compilation mais ce n'est pas une priorité et de toute façon mes connaissance en méta programmation sont trop limitées pour ça alors je suis déjà bien content d'arriver à faire quelque chose qui fonctionne.

            Résolu!

            -
            Edité par OmbreNoire 12 juin 2021 à 13:02:03

            • Partager sur Facebook
            • Partager sur Twitter

            Mauvais std::tuple retourné par la fonction.

            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
            • Editeur
            • Markdown