5

I tried to traverse tree like structures with a generic recursive function without defining a recursive function in global each time for each structures.

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

and I create a tree with CA

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";
...

I can use this macro to traverse this structure and this can work with other tree like structures.

#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);\
    }\
}

It is hard to read but it works fine, I traverse the root of CA like this

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

Works with other structure, like this

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

Traverse the root of MyData

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

There is a major problem here.

I must specifies the type of the object and its container, because the lambda expression here is defined in recursive form.

Can macro deduce type like template function ? or maybe I should achieve this in other way ?

max66
  • 65,235
  • 10
  • 71
  • 111
sainimu78
  • 63
  • 6
  • With help, I get the final tested macro version, I think it is nice, neat. http://coliru.stacked-crooked.com/a/4a3534cce2fa7394 – sainimu78 Jan 31 '19 at 14:44

3 Answers3

1

Not a full answer, but some half-formed thoughts.

I don't believe you absolutely need a macro here. Even if an interface is not absolutely possible, it should be possible by passing pointer-to-members and appropriate functions. You'd probably also need some template specialisation for determining ->* vs. .*, but I haven't thought that far yet.

As a quick proof-of-concept just doing the "size-finding" bit of your function:

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

I realise I've sidestepped your actual question, however. For that I believe that decltype is what you're looking for. You may decide that the macro is more flexible/suited to your needs anyway, but I just wanted to get this out there.

N. Shead
  • 3,828
  • 1
  • 16
  • 22
  • That is cool, I never knew I can get a member pointer like that with new standard .but it turns out I should write longer line, or I need to write some wrapper for the particular structure. – sainimu78 Jan 31 '19 at 15:50
1

Just don't write a generic macro at all. That is a really complex macro that is really difficult to understand and use. It also goes through std::function, so it adds a lot of overhead as an extra bonus? This is simply the wrong approach and you won't get much value from it.

Basically, you just need a recursive lambda. Lambdas cannot be directly recursive in C++ yet, but you can get the job done using something called a Y-Combinator:

auto print_names = y_combinator([](auto self, CA const& item) {
    std::cout << item.name << std::endl;
    for (CA* ca : item.vecChild) {
        self(*ca);
    }
});

You can generalize this with a variable template (it doesn't actually have to be a variable template, you can just write a different recurse function for each type - this just gives everything the same name):

// 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);
        }
    });
};

recurse<CA> takes some function that is invocable on a CA and returns a function that recursively invokes it on a tree of CA.

Which lets you write:

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

This approach lets you write the same kind of thing for your other structures - in normal code:

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);
        }            
    });
};

A full implementation of a Y-Combinator in C++14 would be, from P0200

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));
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • template <> auto recurse = [](auto f) ... I get an internal error from compiler in vs2017. – sainimu78 Feb 02 '19 at 05:17
  • @sainimu78 Internal errors are always compiler bugs, please report it to the Microsoft team. – Barry Feb 02 '19 at 14:01
  • @sainimu78 You can just write these as separate functions - one named `recurse_ca` and one named `recurse_mylist`, I'm not sure why I even wrote them as variable templates. Point is, just using lambdas like this gets you the functionality you want, in a way that's _dramatically_ easier to understand and will also perform dramatically better. – Barry Feb 02 '19 at 14:08
0

There is a major problem here.

I must specifies the type of the object and its container, because the lambda expression here is defined in recursive form.

Can macro deduce type like template function ?

Are you sure a macro is necessary?

Isn't better a template function and some methods, with fixed names, in classes (a sort of interface)?

Anyway, if I understand correctly your macro, instead of TAnyObject you can use decltype(obj) and instead of TAnyContainer you can use decltype(containerNameOfObj)

So something (sorry: code non tested)

#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);\
    }\
}
Community
  • 1
  • 1
max66
  • 65,235
  • 10
  • 71
  • 111