5
votes

c ++, une fonction de modèle récursive générique pour parcourir des structures semblables à des arbres

J'ai essayé de parcourir des structures arborescentes avec une fonction récursive générique sans définir une fonction récursive en global à chaque fois pour chaque structure.

Combinator(root, ->, pLstChild, ->count, ->pItem, MyData*, MyList*, 
[&](auto& pItem, auto& pLst)
{
    std::cout << pItem->name << std::endl;
},
[&](...)
{
});

et je crée un arbre avec CA

struct MyData;
struct MyList
{
    int count;
    MyData* pItem;
};
struct MyData
{
    char* name;
    MyList* pLstChild;
};

Je peux utiliser cette macro pour parcourir cette structure et cela peut fonctionner avec d'autres arborescences comme des structures.

Combinator(root, ->, vecChild, .size(), , CA*, std::vector<CA*>&, 
[&](auto& item, auto& vec)
{
    std::cout << item.name << std::endl;
},
[&](...)
{
});

C'est difficile à lire mais cela fonctionne bien, je traverse la racine de CA comme ceci

#define Combinator(obj, combinatorObjToContainer, containerNameOfObj, invokingGetCount, combinatorContainerToIndexingItem, TAnyObject, TAnyContainer, argEnterFunc, argLeaveFunc)\
{\
    std::function<void(TAnyObject, TAnyContainer)> RecursFunc = [&](auto& argObj, auto& argContainer)\
    {\
        argEnterFunc(argObj, argContainer);\
        for(size_t idx=0; idx<argObj combinatorObjToContainer containerNameOfObj invokingGetCount; ++idx)\
        {\
            RecursFunc(argObj, argObj combinatorObjToContainer containerNameOfObj combinatorContainerToIndexingItem [idx]);\
        }\
        argLeaveFunc(argObj, argContainer);\
    }\
}

Fonctionne avec une autre structure, comme ceci

auto root = new CA;
root->name = "root";
root->push_back(new CA);
auto& childA = root->back();
childA->name = "childA";
root->push_back(new CA);
auto& childB = root->back();
childB->name = "childB";
...

Traversez la racine de MyData

//structure #1
class CA
{
public:
    std::string name;
    std::vector<CA*> vecChild;
};

Il y a un problème majeur ici.

Je dois spécifier le type de l'objet et son conteneur, car l'expression lambda ici est définie sous forme récursive.

Une macro peut-elle déduire un type comme une fonction modèle? ou peut-être que je devrais y parvenir d'une autre manière?


1 commentaires

Avec de l'aide, j'obtiens la version finale de la macro testée, je trouve que c'est sympa, soigné. coliru.stacked-crooked.com/a/4a3534cce2fa7394


3 Réponses :


0
votes

Il y a un problème majeur ici.

Je dois spécifier le type de l'objet et son conteneur, car l'expression lambda ici est définie sous forme récursive.

Une macro peut-elle déduire un type comme une fonction de modèle?

Etes-vous sûr qu'une macro est nécessaire?

N'est-ce pas mieux une fonction de modèle et des méthodes, avec des noms fixes, dans des classes (une sorte d'interface)?

Quoi qu'il en soit , si je comprends bien votre macro, au lieu de TAnyObject vous pouvez utiliser decltype (obj) et au lieu de TAnyContainer vous pouvez utiliser decltype (containerNameOfObj)

Donc quelque chose (désolé: code non testé)

#define Combinator(obj, combinatorObjToContainer, containerNameOfObj, invokingGetCount, combinatorContainerToIndexingItem, argEnterFunc, argLeaveFunc)\
{\
    std::function<void(decltype(obj), decltype(containerNameOfObj))> RecursFunc = [&](auto& argObj, auto& argContainer)\
    {\
        argEnterFunc(argObj, argContainer);\
        for(size_t idx=0; idx<argObj combinatorObjToContainer containerNameOfObj invokingGetCount; ++idx)\
        {\
            RecursFunc(argObj, argObj combinatorObjToContainer containerNameOfObj combinatorContainerToIndexingItem [idx]);\
        }\
        argLeaveFunc(argObj, argContainer);\
    }\
}

0 commentaires

1
votes

Pas une réponse complète, mais des pensées à moitié formées.

Je ne pense pas que vous ayez absolument besoin d'une macro ici. Même si une interface n'est pas absolument possible, elle devrait être possible en passant des pointeurs vers les membres et les fonctions appropriées. Vous auriez probablement également besoin d'une spécialisation de modèle pour déterminer -> * par rapport à . * , mais je n'y ai pas encore pensé.

Comme preuve de concept rapide, faites simplement le bit "recherche de taille" de votre fonction:

template <typename Obj, typename ContainerMemPtr, typename SizeFunc>
void recurseFunc(Obj&& obj, ContainerMemPtr container, SizeFunc func) {
    for (unsigned i = 0; i < func(obj.*container); i++)
        std::cout << i << std::endl;; // fill out this section
}

// ...

CA ca = // ...
recurseFunc(ca, &CA::vecChild, [](const auto& vec){ return vec.size(); });

MyData* data = // ...
recurseFunc(*data, &MyData::pLstChild, [](const auto& list) { return list->count; });

http://coliru.stacked-crooked.com/a/2fd33500e52e5fe7

Je me rends compte que j'ai contourné votre question, toutefois. Pour cela, je crois que decltype est ce que vous recherchez. Vous pouvez décider que la macro est de toute façon plus flexible / adaptée à vos besoins, mais je voulais juste que cela soit disponible.


1 commentaires

C'est cool, je n'ai jamais su que je pouvais obtenir un pointeur de membre comme celui-là avec un nouveau standard. Mais il s'avère que je devrais écrire une ligne plus longue, ou j'ai besoin d'écrire un wrapper pour la structure particulière.



1
votes

N'écrivez pas du tout une macro générique. C'est une macro vraiment complexe qui est vraiment difficile à comprendre et à utiliser. Il passe également par std :: function , donc cela ajoute beaucoup de frais généraux comme bonus supplémentaire? C'est tout simplement la mauvaise approche et vous n'en tirerez pas beaucoup de valeur.

Fondamentalement, vous avez juste besoin d'un lambda récursif. Les lambdas ne peuvent pas encore être directement récursifs en C ++, mais vous pouvez faire le travail en utilisant quelque chose appelé Y-Combinator:

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

Vous pouvez généraliser ceci avec un modèle de variable (il n'est pas nécessaire que ce soit un modèle de variable, vous pouvez simplement écrire une fonction recurse différente pour chaque type - cela donne simplement à tout le même nom):

XXX

recurse prend une fonction invocable sur une CA et retourne une fonction qui l'invoque récursivement sur un arbre de CA .

Ce qui vous permet d'écrire:

template <>
auto recurse<MyList> = [](auto f){
    return y_combinator([=](auto self, auto* list){
        for (int i = 0; i < list->count; ++i) {
            f(list->pItem[i]);
            self(list->pitem[i].pLstChild);
        }            
    });
};

Cette approche vous permet d'écrire le même genre de chose pour vos autres structures - en code normal:

auto print_names = recurse<CA>([](CA const& item){
    std::cout << item.name << std::endl;
});

Une implémentation complète d'un Y-Combinator en C ++ 14 serait, à partir de P0200

// variable template to handle all all tree recursions
template <typename TreeLike>
auto recurse = 0;

template <>
auto recurse<CA> = [](auto f){
    return y_combinator([=](auto self, auto&& ca){
        f(ca);
        for (auto child : ca.vecChild) {
            self(*child);
        }
    });
};


3 commentaires

template <> auto recurse = [] (auto f) ... J'obtiens une erreur interne du compilateur dans vs2017.


@ sainimu78 Les erreurs internes sont toujours des bogues du compilateur, veuillez le signaler à l'équipe Microsoft.


@ sainimu78 Vous pouvez simplement les écrire comme des fonctions séparées - une nommée recurse_ca et une nommée recurse_mylist , je ne sais pas pourquoi je les ai même écrites comme modèles de variables. Le fait est que le simple fait d'utiliser des lambdas comme celui-ci vous permet d'obtenir les fonctionnalités que vous souhaitez, d'une manière dramatiquement plus facile à comprendre et qui fonctionnera considérablement mieux.