c++algorithmc++14template-meta-programmingleast-common-ancestor

Determine least common ancestor at compile-time


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.


Solution

  • 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.