#include <iostream>
#include <unistd.h>
#include <sys/times.h>

#define SUBCLASSES \
    SUBCLASS(A) SUBCLASS(B) SUBCLASS(C) SUBCLASS(D) SUBCLASS(E) \
    SUBCLASS(F) SUBCLASS(G)

#define SUBCLASS(X) struct X ;
SUBCLASSES
#undef SUBCLASS

struct Base;

// Use this as a way to hack template specialization of template method
template <typename MAYBE_DERIVED>
struct BaseIsTypeOrSubtype {
    MAYBE_DERIVED * operator () (Base *) const { return 0; }
};

struct Base {
    Base () { std::cout << __func__ << std::endl; }
    virtual ~Base () {}

    template <typename MAYBE_DERIVED> MAYBE_DERIVED * isTypeOrSubtype () {
        return BaseIsTypeOrSubtype<MAYBE_DERIVED>()(this);
    }

    #define SUBCLASS(X) virtual X * is_##X () { return 0; }
    SUBCLASSES
    #undef SUBCLASS
};

#define SUBCLASS(X) \
    template <> struct BaseIsTypeOrSubtype<X> { \
        X * operator () (Base *b) const { return b->is_##X(); } \
    };
SUBCLASSES
#undef SUBCLASS

#define FOO(X) \
    X () : count_(0) {} \
    X * is_##X () { return this; } \
    void foo () { if (this) { count_ += 1; } } \
    int count () { return this ? count_ : 0; } \
    private: int count_

struct A : virtual Base { FOO(A); };
struct B : A { FOO(B); };
struct C : A { FOO(C); };
struct D : virtual Base { FOO(D); };
struct E : D { FOO(E); };
struct F : D { FOO(F); };
struct G : B, F { FOO(G); };

void foo (Base *b) {
    for (int i = 0; i < 20000000; ++i) {
        b->isTypeOrSubtype<E>()->foo();
    }
}

void foo_dc (Base *b) {
    for (int i = 0; i < 2000000; ++i) {
        dynamic_cast<E *>(b)->foo();
    }
}

void bar (Base *b) {
    for (int i = 0; i < 20000000; ++i) {
        b->isTypeOrSubtype<C>()->foo();
    }
}

void bar_dc (Base *b) {
    for (int i = 0; i < 2000000; ++i) {
        dynamic_cast<C *>(b)->foo();
    }
}

void report (struct tms &start, struct tms &finish, Base *x, int f = 1) {
    double ticks_per_sec = sysconf(_SC_CLK_TCK);
    unsigned long long elapsed = 0;
    elapsed += finish.tms_utime;
    elapsed += finish.tms_stime;
    elapsed -= start.tms_utime;
    elapsed -= start.tms_stime;
    int countC = x->isTypeOrSubtype<C>()->count();
    int countE = x->isTypeOrSubtype<E>()->count();
    std::cout << countC + countE << " hits, "
              << f * elapsed/ticks_per_sec  << " seconds"
              << std::endl;
}

void run_test (void (*test)(Base *), Base *x, int f = 1) {
    struct tms start;
    struct tms finish;
    times(&start);
    test(x);
    times(&finish);
    report(start, finish, x, f);
}

int main () {
    G g;
    C c;
    E e;

    run_test(foo_dc, &g, 10); //--> 0          g is not an E
    run_test(bar_dc, &g, 10); //--> 0          g is not a C

    run_test(foo_dc, &c, 10); //--> 0          c is not an E
    run_test(bar_dc, &c, 10); //--> 2000000    c is a C

    run_test(foo_dc, &e, 10); //--> 2000000    e is an E
    run_test(bar_dc, &e, 10); //--> 2000000    e is not a C
}
