c++,一个遍历树状结构的通用递归模板函数
c++, a generic recursive template function to traverse tree like structures
我尝试使用通用递归函数遍历树状结构,而不是每次都在全局中为每个结构定义递归函数。
//structure #1
class CA
{
public:
std::string name;
std::vector<CA*> vecChild;
};
然后我用 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";
...
我可以使用这个宏来遍历这个结构,这可以与其他类似树的结构一起使用。
#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);\
}\
}
读起来有点吃力,但是很好用,我是这样遍历CA根的
Combinator(root, ->, vecChild, .size(), , CA*, std::vector<CA*>&,
[&](auto& item, auto& vec)
{
std::cout << item.name << std::endl;
},
[&](...)
{
});
与其他结构一起工作,像这样
struct MyData;
struct MyList
{
int count;
MyData* pItem;
};
struct MyData
{
char* name;
MyList* pLstChild;
};
遍历 MyData 的根
Combinator(root, ->, pLstChild, ->count, ->pItem, MyData*, MyList*,
[&](auto& pItem, auto& pLst)
{
std::cout << pItem->name << std::endl;
},
[&](...)
{
});
这里有个大问题。
我必须指定对象的类型及其容器,因为这里的lambda表达式是以递归形式定义的。
宏可以像模板函数一样推导类型吗?或者也许我应该以其他方式实现这一目标?
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 ?
您确定需要宏吗?
在类(一种接口)中,模板函数和一些具有固定名称的方法不是更好吗?
无论如何,如果我理解正确你的宏,你可以使用 decltype(obj)
而不是 TAnyObject
而不是 TAnyContainer
你可以使用 decltype(containerNameOfObj)
所以(抱歉:代码未测试)
#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);\
}\
}
不是一个完整的答案,而是一些半成形的想法。
我认为您绝对不需要这里的宏。即使接口不是绝对可行的,也应该可以通过传递指向成员的指针和适当的函数来实现。您可能还需要一些模板专业化来确定 ->*
与 .*
,但我还没有想那么远。
作为快速概念验证,只需执行函数的 "size-finding" 位即可:
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
不过,我知道我回避了你的实际问题。为此,我相信 decltype
就是您要找的。无论如何,您可能会认为该宏更 flexible/suited 满足您的需求,但我只是想把它放在那里。
根本就不要编写通用宏。这是一个非常复杂的宏,很难理解和使用。它还通过 std::function
,因此它会增加很多开销作为额外奖励?这是完全错误的方法,您不会从中获得太多价值。
基本上,您只需要一个递归 lambda。 Lambda 在 C++ 中还不能 直接 递归,但是你可以使用一种叫做 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);
}
});
你可以用一个变量模板来概括它(它实际上不必是一个变量模板,你可以为每种类型写一个不同的 recurse
函数——这只是给所有东西都一个相同的名字) :
// 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>
接受一些可以在 CA
上调用的函数,returns 一个函数在 CA
.
的树上递归调用它
这让你写:
auto print_names = recurse<CA>([](CA const& item){
std::cout << item.name << std::endl;
});
这种方法可以让你为你的其他结构编写同样的东西——在普通代码中:
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);
}
});
};
在 C++14 中 Y-Combinator 的完整实现是 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));
}
我尝试使用通用递归函数遍历树状结构,而不是每次都在全局中为每个结构定义递归函数。
//structure #1
class CA
{
public:
std::string name;
std::vector<CA*> vecChild;
};
然后我用 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";
...
我可以使用这个宏来遍历这个结构,这可以与其他类似树的结构一起使用。
#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);\
}\
}
读起来有点吃力,但是很好用,我是这样遍历CA根的
Combinator(root, ->, vecChild, .size(), , CA*, std::vector<CA*>&,
[&](auto& item, auto& vec)
{
std::cout << item.name << std::endl;
},
[&](...)
{
});
与其他结构一起工作,像这样
struct MyData;
struct MyList
{
int count;
MyData* pItem;
};
struct MyData
{
char* name;
MyList* pLstChild;
};
遍历 MyData 的根
Combinator(root, ->, pLstChild, ->count, ->pItem, MyData*, MyList*,
[&](auto& pItem, auto& pLst)
{
std::cout << pItem->name << std::endl;
},
[&](...)
{
});
这里有个大问题。
我必须指定对象的类型及其容器,因为这里的lambda表达式是以递归形式定义的。
宏可以像模板函数一样推导类型吗?或者也许我应该以其他方式实现这一目标?
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 ?
您确定需要宏吗?
在类(一种接口)中,模板函数和一些具有固定名称的方法不是更好吗?
无论如何,如果我理解正确你的宏,你可以使用 decltype(obj)
而不是 TAnyObject
而不是 TAnyContainer
你可以使用 decltype(containerNameOfObj)
所以(抱歉:代码未测试)
#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);\
}\
}
不是一个完整的答案,而是一些半成形的想法。
我认为您绝对不需要这里的宏。即使接口不是绝对可行的,也应该可以通过传递指向成员的指针和适当的函数来实现。您可能还需要一些模板专业化来确定 ->*
与 .*
,但我还没有想那么远。
作为快速概念验证,只需执行函数的 "size-finding" 位即可:
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
不过,我知道我回避了你的实际问题。为此,我相信 decltype
就是您要找的。无论如何,您可能会认为该宏更 flexible/suited 满足您的需求,但我只是想把它放在那里。
根本就不要编写通用宏。这是一个非常复杂的宏,很难理解和使用。它还通过 std::function
,因此它会增加很多开销作为额外奖励?这是完全错误的方法,您不会从中获得太多价值。
基本上,您只需要一个递归 lambda。 Lambda 在 C++ 中还不能 直接 递归,但是你可以使用一种叫做 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);
}
});
你可以用一个变量模板来概括它(它实际上不必是一个变量模板,你可以为每种类型写一个不同的 recurse
函数——这只是给所有东西都一个相同的名字) :
// 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>
接受一些可以在 CA
上调用的函数,returns 一个函数在 CA
.
这让你写:
auto print_names = recurse<CA>([](CA const& item){
std::cout << item.name << std::endl;
});
这种方法可以让你为你的其他结构编写同样的东西——在普通代码中:
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);
}
});
};
在 C++14 中 Y-Combinator 的完整实现是 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)); }