Reputation: 8273
There are tons of questions about the least common ancestor algorithm, but this one is different because I'm trying to determine the LCA at compile-time, and my tree is neither binary nor a search tree, even though my simplified version may look like one.
Suppose you have a bunch of structures which contain a member typedef parent
, which is another similar structure:
struct G
{
typedef G parent; // 'root' node has itself as parent
};
struct F
{
typedef G parent;
};
struct E
{
typedef G parent;
};
struct D
{
typedef F parent;
};
struct C
{
typedef F parent;
};
struct B
{
typedef E parent;
};
struct A
{
typedef E parent;
};
which together make a tree such as
A B C D
\ / \ /
E F
\ /
\ /
\ /
G
NOTE: There is no inheritance relationship between the structures.
What I would like to do is create a type-trait least_common_ancestor
such that:
least_common_ancestor<A, B>::type; // E
least_common_ancestor<C, D>::type; // F
least_common_ancestor<A, E>::type; // E
least_common_ancestor<A, F>::type; // G
What's the best way to do this?
I'm not concerned with algorithmic complexity particularly since the tree depth is small, rather I'm looking for the simplest meta-program that that will get to the correct answer.
EDIT: I need to be able to build the solution with msvc2013, among other compilers, so answers without constexpr
are preferred.
Upvotes: 20
Views: 778
Reputation: 48457
template <typename...> struct typelist {};
template <typename T, typename... Ts>
struct path : path<typename T::parent, T, Ts...> {};
template <typename T, typename... Ts>
struct path<T, T, Ts...> { using type = typelist<T, Ts...>; };
template <typename T, typename U>
struct least;
template <typename T, typename... Vs, typename... Us>
struct least<typelist<T, Vs...>, typelist<T, Us...>> { using type = T; };
template <typename T, typename W, typename... Vs, typename... Us>
struct least<typelist<T, W, Vs...>, typelist<T, W, Us...>>
: least<typelist<W, Vs...>, typelist<W, Us...>> {};
template <typename V, typename U>
using least_common_ancestor = least<typename path<V>::type, typename path<U>::type>;
Bottom-up: Form paths (path::type
) from both nodes to the root by means of prepending a typelist at each level (path<?, T, Ts...>
), until parent
equals the currently processed node (<T, T, ?...>
). Moving upwards is performed by replacing T
by T::parent
.
Top-down: Descend the two typelists simultaneously (least
), until there's a mismatch at corresponding positions (Vs..., Us...
); if so, the last common node is the common ancestor (T
); otherwise (<T, W, ?...>, <T, W, ?...>
), remove the matching node (T
) and step one level down (now W
is the last known common node).
Upvotes: 10
Reputation: 303087
This is probably not the most algorithmically efficient approach, but it's functional.
First, we're going to create lists out of the ancestors of each type. So for A
this would be <A,E,G>
and for G
this would be <G>
:
template <class X>
using parent_t = typename X::parent;
template <class... > struct typelist {};
template <class T> struct tag_t { using type = T; };
template <class, class> struct concat;
template <class X, class Y> using concat_t = typename concat<X, Y>::type;
template <class... Xs, class... Ys>
struct concat<typelist<Xs...>, typelist<Ys...>>
: tag_t<typelist<Xs..., Ys...>>
{ };
template <class X, class = parent_t<X>>
struct ancestors
: tag_t<concat_t<typelist<X>, typename ancestors<parent_t<X>>::type>>
{ };
template <class X>
struct ancestors<X, X>
: tag_t<typelist<X>>
{ };
template <class X>
using ancestors_t = typename ancestors<X>::type;
Then the least common ancestor of two nodes is just going to be the first node in one node's ancestors that is contained in the other node's ancestors:
template <class X, class TL> struct contains;
template <class X, class TL> using contains_t = typename contains<X, TL>::type;
template <class X, class... Xs>
struct contains<X, typelist<X, Xs...>> : std::true_type { };
template <class X, class Y, class... Xs>
struct contains<X, typelist<Y, Xs...>> : contains_t<X, typelist<Xs...>> { };
template <class X>
struct contains<X, typelist<>> : std::false_type { };
template <class X, class Y>
struct lca_impl;
template <class X, class Y>
struct lca : lca_impl<ancestors_t<X>, ancestors_t<Y>> { };
template <class X, class... Xs, class TL>
struct lca_impl<typelist<X, Xs...>, TL>
: tag_t<
typename std::conditional_t<contains_t<X, TL>::value,
tag_t<X>,
lca_impl<typelist<Xs...>, TL>
>::type
>
{ };
template <class X, class Y>
using lca_t = typename lca<X, Y>::type;
which has the behavior you'd expect:
static_assert(std::is_same<lca_t<A, E>, E>{}, "!");
static_assert(std::is_same<lca_t<A, D>, G>{}, "!");
Upvotes: 6
Reputation: 37616
This may probably be improved, but you could first compute the depth of your type and then use this information to go up one branch or the other:
template <typename U, typename = typename U::parent>
struct depth {
static const int value = depth<typename U::parent>::value + 1;
};
template <typename U>
struct depth<U, U> {
static const int value = 0;
};
The above will basically compute the depth of your type in your tree.
Then you could use std::enable_if
:
template <typename U, typename V, typename Enabler = void>
struct least_common_ancestor;
template <typename U>
struct least_common_ancestor<U, U> {
using type = U;
};
template <typename U, typename V>
struct least_common_ancestor<U, V,
typename std::enable_if<(depth<U>::value < depth<V>::value)>::type> {
using type = typename least_common_ancestor<U, typename V::parent>::type;
};
template <typename U, typename V>
struct least_common_ancestor<U, V,
typename std::enable_if<(depth<V>::value < depth<U>::value)>::type> {
using type = typename least_common_ancestor<V, typename U::parent>::type;
};
template <typename U, typename V>
struct least_common_ancestor<U, V,
typename std::enable_if<!std::is_same<U, V>::value && (depth<V>::value == depth<U>::value)>::type> {
using type = typename least_common_ancestor<typename V::parent, typename U::parent>::type;
};
Output:
int main(int, char *[]) {
std::cout << std::is_same<least_common_ancestor<A, B>::type, E>::value << std::endl;
std::cout << std::is_same<least_common_ancestor<C, D>::type, F>::value << std::endl;
std::cout << std::is_same<least_common_ancestor<A, E>::type, E>::value << std::endl;
std::cout << std::is_same<least_common_ancestor<A, F>::type, G>::value << std::endl;
std::cout << std::is_same<least_common_ancestor<A, A>::type, A>::value << std::endl;
return 0;
}
Gives:
1 1 1 1 1
This may probably be improved but could be used as a starting point.
Upvotes: 12