【发布时间】:2015-04-04 22:51:54
【问题描述】:
为了对嵌套的类型包(即类型树)进行前序遍历,然后在每个叶子上执行一个操作,我已经制定了一个算法(并且经过测试可以正常工作):
template <typename T>
struct HasChildren : std::false_type {};
template <template <typename...> class P, typename... Types>
struct HasChildren<P<Types...>> : std::true_type {};
template <typename, typename> struct Merge;
template <template <typename...> class P1, template <typename...> class P2, typename... Ts, typename... Us>
struct Merge<P1<Ts...>, P2<Us...>> {
using type = P1<Ts..., Us...>;
};
template <typename, typename> struct Visit;
template <typename, typename, bool> struct VisitHelper;
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct Visit<P1<First, Rest...>, P2<Visited...>> :
VisitHelper<P1<First, Rest...>, P2<Visited...>, HasChildren<First>::value> {};
template <template <typename...> class P1, template <typename...> class P2, typename... Visited>
struct Visit<P1<>, P2<Visited...>> { // End of recursion. Every leaf in the tree visited.
using result = P2<Visited...>;
};
template <typename, typename> struct LeafAction;
// As a simple example, the leaf action is appending its type to P<Visited...>.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct LeafAction<P1<First, Rest...>, P2<Visited...>> : Visit<P1<Rest...>, P2<Visited..., First>> {}; // Having visited First, now visit Rest...
// Here First has children, so visit its children, after which visit Rest...
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, true> : Visit<typename Merge<First, P1<Rest...>>::type, P2<Visited...>> {};
// Here First has no children, so the "leaf action" is carried out.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, false> : LeafAction<P1<First, Rest...>, P2<Visited...>> {};
我的“叶子动作”只是存储访问过的每个叶子的类型,正如我的测试所示:
template <typename...> struct Pack {};
template <typename...> struct Group {};
template <typename...> struct Wrap {};
struct Object {};
int main() {
std::cout << std::boolalpha << std::is_same<
Visit<Pack<Wrap<int, Object, double>, bool, Group<char, Pack<int, double, Pack<char, Wrap<char, long, short>, int, Object>, char>, double>, long>, Pack<>>::type,
Pack<int, Object, double, bool, char, int, double, char, char, long, short, int, Object, char, double, long>
>::value << std::endl; // true
}
但我现在的问题是:如果要在每个非叶子上执行一个动作,并且在访问孩子之后执行这种“非叶子动作”怎么办?如何记住非叶?
这是一个需要此操作的示例任务:
参考我上面的Visit 程序,在访问节点的每个子节点之后(但不是之前),将非叶包中的第一个类型附加到P<Visited...> 包。如果访问了叶子,则将其类型附加到 P<Visited...> 包中,就像在原始程序中一样。由于这个特定的顺序,Visit<Pack<Types...>>::type 也将有一个特定的顺序。解决这个问题,原来的问题就解决了。
如果在访问其子项之前执行该非叶操作,则以下是简单的解决方案:
#include <iostream>
#include <type_traits>
#include <typeinfo>
template <typename T>
struct HasChildren : std::false_type {};
template <template <typename...> class P, typename... Types>
struct HasChildren<P<Types...>> : std::true_type {};
template <typename, typename> struct Merge;
template <template <typename...> class P1, template <typename...> class P2, typename... Ts, typename... Us>
struct Merge<P1<Ts...>, P2<Us...>> {
using type = P1<Ts..., Us...>;
};
template <typename> struct FirstType;
template <template <typename...> class P, typename First, typename... Rest>
struct FirstType<P<First, Rest...>> {
using type = First;
};
template <typename, typename> struct Visit;
template <typename, typename, bool> struct VisitHelper;
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct Visit<P1<First, Rest...>, P2<Visited...>> :
VisitHelper<P1<First, Rest...>, P2<Visited...>, HasChildren<First>::value> {};
template <template <typename...> class P1, template <typename...> class P2, typename... Visited>
struct Visit<P1<>, P2<Visited...>> { // End of recursion. Every leaf in the tree visited.
using result = P2<Visited...>;
};
template <typename, typename> struct LeafAction;
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct LeafAction<P1<First, Rest...>, P2<Visited...>> : Visit<P1<Rest...>, P2<Visited..., First>> {};
template <typename, typename> struct NonLeafActionPrevisit;
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct NonLeafActionPrevisit<P1<First, Rest...>, P2<Visited...>> :
Visit<typename Merge<First, P1<Rest...>>::type, P2<Visited..., typename FirstType<First>::type>> {};
// Here First has children, so visit its children, after which visit Rest..., but before doing so carry out the non-leaf action in the inherited struct.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, true> :
NonLeafActionPrevisit<P1<First, Rest...>, P2<Visited...>> {}; // *** The simple solution here.
// Here First has no children, so the "leaf action" is carried out. In this case, it is appended to P<Visited...> as a simple example.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, false> : LeafAction<P1<First, Rest...>, P2<Visited...>> {};
template <typename> struct VisitTree;
template <template <typename...> class P, typename... Types>
struct VisitTree<P<Types...>> : Visit<P<Types...>, P<>> {};
// ---------------------------- Testing ----------------------------
template <typename...> struct Pack {};
template <typename...> struct Group {};
template <typename...> struct Wrap {};
struct Object {};
int main() {
std::cout << std::boolalpha << std::is_same<
VisitTree<Pack<Wrap<int, Object, double>, bool, Group<char, Pack<int, double, Pack<char, Wrap<char, long, short>, int, Object>, char>, double>, long>>::result,
Pack<int, int, Object, double, bool, char, char, int, int, double, char, char, char, char, long, short, int, Object, char, double, long>
>::value << std::endl; // true
}
如果要在探望孩子之后进行非叶行动,那么解决方案是什么? 在这种情况下,输出会有所不同,即:
Pack<int, Object, double, int, bool, char, int, double, char, char, long, short, char, int, Object, int, char, char, double, long>
想法:从 First 获取最后一个孩子。将最后一个孩子和 First 存储在某个地方(但在哪里???)。当最后一个孩子被访问时,对 First 执行非叶操作。比如:
template <typename, typename, typename> struct Visit;
template <typename, typename, typename, bool> struct VisitHelper;
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... ChildAndParent, typename... Visited>
struct Visit<P1<First, Rest...>, P2<ChildAndParent...>, P3<Visited...>> :
VisitHelper<P1<First, Rest...>, P2<ChildAndParent...>, P3<Visited...>, HasChildren<First>::value> {};
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename... ChildAndParent, typename... Visited>
struct Visit<P1<>, P2<ChildAndParent...>, P3<Visited...>> { // End of recursion. Every leaf in the tree visited.
using type = P3<Visited...>;
};
// Here First has children, so visit its children, after which visit Rest...
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... ChildAndParent, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<ChildAndParent...>, P3<Visited...>, true> :
Visit<typename Merge<First, P2<Rest...>>::type, P2<ChildAndParent...,
typename LastType<First>::type, First>, P3<Visited...>> {};
// Idea: Get the last child from First. Store that last child and First here. When that last child is visited, carry out the non-leaf action on First.
// Here First has no children, so the "leaf action" is carried out. In this case, it is appended to P<Visited...> as a simple example.
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... ChildAndParent, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<ChildAndParent...>, P3<Visited...>, false> :
Visit<P1<Rest...>, P2<ChildAndParent...>, P3<Visited..., First>> {};
现在最困难的部分是正确使用P2<ChildAndParent...>,假设这个想法完全可行。
更新(12 小时后):好吧,我尽力了。这是我想出的,它可以编译,但它让我无法理解为什么我仍然得到错误的结果。也许有人可以对此有所了解。
#include <iostream>
#include <type_traits>
#include <typeinfo>
template <typename T>
struct HasChildren : std::false_type {};
template <template <typename...> class P, typename... Types>
struct HasChildren<P<Types...>> : std::true_type {};
template <typename, typename> struct Merge;
template <template <typename...> class P1, template <typename...> class P2, typename... Ts, typename... Us>
struct Merge<P1<Ts...>, P2<Us...>> {
using type = P1<Ts..., Us...>;
};
template <typename> struct FirstType;
template <template <typename...> class P, typename First, typename... Rest>
struct FirstType<P<First, Rest...>> {
using type = First;
};
template <typename> struct LastType;
template <template <typename...> class P, typename Last>
struct LastType<P<Last>> {
using type = Last;
};
template <template <typename...> class P, typename First, typename... Rest>
struct LastType<P<First, Rest...>> : LastType<P<Rest...>> {};
template <typename, typename...> struct ExistsInPack;
template <typename T, typename First, typename... Rest>
struct ExistsInPack<T, First, Rest...> : ExistsInPack<T, Rest...> {};
template <typename T, typename... Rest>
struct ExistsInPack<T, T, Rest...> : std::true_type {};
template <typename T>
struct ExistsInPack<T> : std::false_type {};
template <typename Child, typename First, typename Second, typename... Rest>
struct GetParent : GetParent<Child, Rest...> {};
template <typename Child, typename Parent, typename... Rest>
struct GetParent<Child, Child, Parent, Rest...> {
using type = Parent;
};
template <typename, typename, typename> struct RemoveChildAndParentHelper;
template <template <typename...> class P, typename Child, typename First, typename Second, typename... Rest, typename... Accumulated>
struct RemoveChildAndParentHelper<Child, P<First, Second, Rest...>, P<Accumulated...>> : RemoveChildAndParentHelper<Child, P<Rest...>, P<Accumulated..., First, Second>> {};
template <template <typename...> class P, typename Child, typename Parent, typename... Rest, typename... Accumulated>
struct RemoveChildAndParentHelper<Child, P<Child, Parent, Rest...>, P<Accumulated...>> {
using type = P<Accumulated..., Rest...>;
};
template <template <typename...> class P, typename Child, typename... Accumulated>
struct RemoveChildAndParentHelper<Child, P<>, P<Accumulated...>> {
using type = P<Accumulated...>;
};
template <typename, typename> struct RemoveChildAndParent;
template <template <typename...> class P, typename Child, typename... LastChildAndParent>
struct RemoveChildAndParent<Child, P<LastChildAndParent...>> : RemoveChildAndParentHelper<Child, P<LastChildAndParent...>, P<>> {};
template <typename, typename, typename> struct Visit;
template <typename, typename, typename, bool> struct VisitHelper;
template <typename, typename, typename, bool> struct CheckIfLastChild;
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... LastChildAndParent, typename... Visited>
struct Visit<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>> :
VisitHelper<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>, HasChildren<First>::value> {};
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename... LastChildAndParent, typename... Visited>
struct Visit<P1<>, P2<LastChildAndParent...>, P3<Visited...>> { // End of recursion. Every leaf in the tree visited.
using result = P3<Visited...>;
using storage = P2<LastChildAndParent...>; // just for checking (remove later)
};
// Here First has children, so visit its children, after which visit Rest...
// Get the last child from First. Store that last child and First here. When that last child is visited later on, carry out the non-leaf action on First.
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... LastChildAndParent, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>, true> :
Visit<typename Merge<First, P2<Rest...>>::type, P2<LastChildAndParent..., typename LastType<First>::type, First>, P3<Visited...>> {};
// Here First has no children, so the "leaf action" is carried out. In this case, it is appended to P<Visited...>.
// Check if First is a last child. If so the non-leaf action of its parent is to be carried out immediately after First's action is carried out.
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... LastChildAndParent, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>, false> :
CheckIfLastChild<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>, ExistsInPack<First, LastChildAndParent...>::value> {};
// First is a last child (and is a leaf), so append First to P3<Visited...> (the leaf action), and then append the first type of its parent to P3<Visited...> after it (the non-leaf action).
// First and its parent must be removed from P2<LastChildAndParent...> at this point.
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... LastChildAndParent, typename... Visited>
struct CheckIfLastChild<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>, true> :
Visit<P1<Rest...>, typename RemoveChildAndParent<First, P2<LastChildAndParent...>>::type, P3<Visited..., First, typename FirstType<typename GetParent<First, LastChildAndParent...>::type>::type>> {};
// First is not a last child (but is a leaf), so append First to P3<Visited...> (the leaf action) and proceed with visiting the next type in P1<Rest...>.
template <template <typename...> class P1, template <typename...> class P2, template <typename...> class P3, typename First, typename... Rest, typename... LastChildAndParent, typename... Visited>
struct CheckIfLastChild<P1<First, Rest...>, P2<LastChildAndParent...>, P3<Visited...>, false> : Visit<P1<Rest...>, P2<LastChildAndParent...>, P3<Visited..., First>> {};
template <typename> struct VisitTree;
template <template <typename...> class P, typename... Types>
struct VisitTree<P<Types...>> : Visit<P<Types...>, P<>, P<>> {};
// ---------------------------- Testing ----------------------------
template <typename...> struct Pack;
template <typename Last>
struct Pack<Last> {
static void print() {std::cout << typeid(Last).name() << std::endl;}
};
template <typename First, typename... Rest>
struct Pack<First, Rest...> {
static void print() {std::cout << typeid(First).name() << ' '; Pack<Rest...>::print();}
};
template <>
struct Pack<> {
static void print() {std::cout << "empty" << std::endl;}
};
template <typename...> struct Group {};
template <typename...> struct Wrap {};
struct Object {};
int main() {
using Tree = VisitTree<Pack<Wrap<int, Object, double>, bool, Group<char, Pack<int, double, Pack<char, Wrap<char, long, short>, int, Object>, char>, double>, long>>;
Tree::result::print(); // int Object double int bool char int double char char int char long short int Object char char double long
Tree::storage::print(); // empty
std::cout << std::boolalpha << std::is_same<
Tree::result,
Pack<int, Object, double, int, bool, char, int, double, char, char, long, short, char, int, Object, char, char, int, char, double, long>
>::value << std::endl; // false
}
如果您想知道,这是我这样做的动机: 考虑这个循环(显然是在运行时执行的):
template <int N>
void Graph<N>::topologicalSortHelper (int v, std::array<bool, N>& visited, std::stack<int>& stack) {
visited[v] = true; // Mark the current node as visited.
for (int x : adjacentVertices[v]) // Repeat for all the vertices adjacent to this vertex.
if (!visited[x])
topologicalSortHelper (x, visited, stack);
stack.push(v); // Push current vertex to 'stack' which stores the result.
}
这里有 2 个“非叶子操作”。 visited[v] = true;是在访问孩子之前执行的,所以没问题(只是在继承中进行更改),但真正的问题是stack.push(v);,这是在访问孩子之后执行的。最后,我希望Graph<6, 5,2, 5,0, 4,0, 4,1, 2,3, 3,1>::topological_sort 成为编译时结果index_sequence<5,4,2,3,1,0>,其中6 是顶点数,5,2, 5,0, 4,0, 4,1, 2,3, 3,1 描述图的边缘。那是我正在做的真正的项目,我几乎完成了。解决上面的通用问题,这个问题我就解决了。
更新:我发现了我的逻辑错误。行:
Visit<typename Merge<First, P2<Rest...>>::type, P2<LastChildAndParent..., typename LastType<First>::type, First>, P3<Visited...>>
不会唯一标识最后一个子节点的位置,因为树中可能有 other 叶子类型为 First。这表明:
必须重新设计原始树,为每个节点使用唯一的序列号(最后的手段,因为这会迫使客户端代码更改),或者
算法应在遍历树中的每个节点时为其分配唯一的序列 ID。第二个想法是理想的,因为不需要重新设计原始树,但它更具挑战性。例如,已知存在但尚未在遍历中访问过的孩子的 ID 号是多少?看起来分支编号必须用于标识每个节点。
顺便说一句,我设法解决了我最初的图的编译时拓扑排序问题: http://ideone.com/9DKh4s
它使用了这个线程中正在制定的模式,我很幸运每个顶点都有唯一的节点值。但是我仍然想知道在树的节点没有唯一值的情况下的一般解决方案,而不将唯一的序列ID与原始树的每个节点相邻(这严重丑化了原始树的设计),即携带解决上述#2,或类似的东西)。
更新(经过几天的思考)。现在正在研究一个新想法,这可能会激发对这个问题感兴趣的任何人:
template <typename, typename, typename> struct NonLeafActionPostvisit;
// As a simple example, the postvisit non-leaf action is appending the first type of the pack to P<Visited...>.
template <template <typename...> class P1, template <typename...> class P2, typename ChildrenVisits, typename First, typename... Rest, typename... Visited>
struct NonLeafActionPostvisit<ChildrenVisits, P1<First, Rest...>, P2<Visited...>> :
Visit<P1<Rest...>, P2<Visited..., typename FirstType<First>::type>> {};
// Postvisit:
// Here First has children, so visit its children, after which carry out the postvisit non-leaf action, and then visit Rest...
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, true> :
NonLeafActionPostvisit<Visit<First, P2<Visited...>>, P1<First, Rest...>, P2<Visited...>> {};
// Here First has no children, so the "leaf action" is carried out. In this case, it is appended to P<Visited...> as a simple example.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, false> :
LeafAction<P1<First, Rest...>, P2<Visited...>> {};
虽然它还没有给出正确的结果,但如果它最终起作用,它似乎比我已经勾勒出的想法要优雅得多。
【问题讨论】:
-
只是一个提示:您可以在测试/示例中使用
int[1]、int[2]等类型,而不是int、Object、..。以我的经验,这提高了可读性。 (可能通过struct t {};缩写为t[1]、t[2]等) -
前序遍历是指先访问根节点,再访问子节点。后订单,这似乎是您想要的,意味着先访问孩子,然后再访问根。两者在实现上的唯一区别是“访问”调用是发生在递归调用之前还是之后。无需在任何地方存储节点即可获得所需的顺序,因为调用堆栈会为您执行此操作。
-
@povman。 constexpr 函数使用单个 return 语句并最终执行模板将执行的操作,因此它们是等效的方法,即每个 constexpr 函数都有一个模板模拟,对吧?
-
经过进一步思考,我意识到这个模板程序不能翻译成 constexpr 函数,因为它的主要结果是一个类型,而不是一个值。