C++, универсальная рекурсивная шаблонная функция для обхода древовидных структур
Я пытался обходить древовидные структуры с помощью универсальной рекурсивной функции, не определяя рекурсивную функцию в глобальном каждый раз для каждой структуры.
//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;
},
[&](...)
{
});
Здесь есть большая проблема.
Я должен указать тип объекта и его контейнер, потому что лямбда-выражение здесь определено в рекурсивной форме.
Может ли макрос выводить тип как функцию шаблона? а может я должен добиться этого другим способом?
3 ответа
Здесь есть большая проблема.
Я должен указать тип объекта и его контейнер, потому что лямбда-выражение здесь определено в рекурсивной форме.
Может ли макрос выводить тип как функцию шаблона?
Вы уверены, что макрос необходим?
Разве не лучше шаблонная функция и некоторые методы с фиксированными именами в классах (своего рода интерфейс)?
Во всяком случае, если я правильно понимаю ваш макрос, а не TAnyObject
ты можешь использовать decltype(obj)
и вместо 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);\
}\
}
Просто не пишите общий макрос вообще. Это действительно сложный макрос, который действительно трудно понять и использовать. Это также проходит std::function
Таким образом, это добавляет много накладных расходов в качестве дополнительного бонуса? Это просто неправильный подход, и вы не получите от него особой пользы.
По сути, вам просто нужна рекурсивная лямбда. Лямбда не может быть прямо рекурсивной в 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
и возвращает функцию, которая рекурсивно вызывает его на дереве 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);
}
});
};
Полная реализация Y-Combinator в C++14 будет, начиная с 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)); }
Не полный ответ, а несколько наполовину сформированных мыслей.
Я не верю, что вам нужен макрос здесь. Даже если интерфейс не совсем возможен, это должно быть возможно путем передачи указателей на члены и соответствующих функций. Возможно, вам также понадобится некоторая специализация шаблона для определения ->*
против .*
, но я еще не думал, что далеко.
В качестве быстрого подтверждения концепции просто выполняя "поиск размера" вашей функции:
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
это то, что вы ищете. Вы можете решить, что макрос в любом случае более гибок / соответствует вашим потребностям, но я просто хотел получить это.