Search code examples
c++c++11lambdafunctional-programmingpolymorphism

"type-switch" construct in C++11


All the time, I find myself doing something like this:

Animal *animal = ...
if (Cat *cat = dynamic_cast<Cat *>(animal)) {
    ...
}
else if (Dog *dog = dynamic_cast<Dog *>(animal)) {
    ...
}
else { assert(false); }

Once I see closures in C++11, I wonder, is something like this possible?

Animal *animal = ...
typecase(animal,
    [](Cat *cat) {
        ...
    },
    [](Dog *dog) {
        ...
    });

Implementing typecase should have been easy, but I keep running into a problem where it can't figure out the function's argument, so it can't know what to try to dynamic_cast to, because it's hard to deduce lambdas' parameters. Took a few days of searching google and SO, but finally figured it out, so I'll share my answer below.


Solution

  • Thanks to an answer from ecatmur at https://stackoverflow.com/a/13359520 I was able to extract the signature from the lambda. The full solution looks like this:

    // Begin ecatmur's code
    template<typename T> struct remove_class { };
    template<typename C, typename R, typename... A>
    struct remove_class<R(C::*)(A...)> { using type = R(A...); };
    template<typename C, typename R, typename... A>
    struct remove_class<R(C::*)(A...) const> { using type = R(A...); };
    template<typename C, typename R, typename... A>
    struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); };
    template<typename C, typename R, typename... A>
    struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); };
    
    template<typename T>
    struct get_signature_impl { using type = typename remove_class<
        decltype(&std::remove_reference<T>::type::operator())>::type; };
    template<typename R, typename... A>
    struct get_signature_impl<R(A...)> { using type = R(A...); };
    template<typename R, typename... A>
    struct get_signature_impl<R(&)(A...)> { using type = R(A...); };
    template<typename R, typename... A>
    struct get_signature_impl<R(*)(A...)> { using type = R(A...); };
    template<typename T> using get_signature = typename get_signature_impl<T>::type;
    // End ecatmur's code
    
    // Begin typecase code
    template<typename Base, typename FirstSubclass, typename... RestOfSubclasses>
    void typecase(
            Base *base,
            FirstSubclass &&first,
            RestOfSubclasses &&... rest) {
    
        using Signature = get_signature<FirstSubclass>;
        using Function = std::function<Signature>;
    
        if (typecaseHelper(base, (Function)first)) {
            return;
        }
        else {
            typecase(base, rest...);
        }
    }
    template<typename Base>
    void typecase(Base *) {
        assert(false);
    }
    template<typename Base, typename T>
    bool typecaseHelper(Base *base, std::function<void(T *)> func) {
        if (T *first = dynamic_cast<T *>(base)) {
            func(first);
            return true;
        }
        else {
            return false;
        }
    }
    // End typecase code
    

    and an example usage is here:

    class MyBaseClass {
    public:
        virtual ~MyBaseClass() { }
    };
    class MyDerivedA : public MyBaseClass { };
    class MyDerivedB : public MyBaseClass { };
    
    
    int main() {
        MyBaseClass *basePtr = new MyDerivedB();
    
        typecase(basePtr,
            [](MyDerivedA *a) {
                std::cout << "is type A!" << std::endl;
            },
            [](MyDerivedB *b) {
                std::cout << "is type B!" << std::endl;
            });
    
        return 0;
    }
    

    If anyone has any improvements, please tell me!