“type-switch” construct in C++11

后端 未结 5 1973
自闭症患者
自闭症患者 2020-12-14 04:35

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

Animal *animal = ...
if (Cat *cat = dynamic_cast(animal)) {
    ...
}
else if (Dog *dog =         


        
相关标签:
5条回答
  • 2020-12-14 04:38

    Implementation

    template <typename T, typename B>
    void action_if(B* value, std::function<void(T*)> action)
    {
        auto cast_value = dynamic_cast<T*>(value);
        if (cast_value != nullptr)
        {
            action(cast_value);
        }
    }
    

    Usage

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

    I don't have access to a C++11 compiler right this second to try this out, but I hope the idea is useful. Depending on how much type inference the compiler is capable of, you may or may not have to specify the case's type twice - I'm not C++11-pro enough to tell from looking at it.

    0 讨论(0)
  • 2020-12-14 04:38

    I think it depends if you want do this at compile time or at run time. For compile time, Verdagon's answer is better, at runtime you can do something like this

    class A {
    };
    
    class B {
    };
    
    void doForA() {
        std::cout << "I'm A" << std::endl;
    }
    
    void doForB() {
        std::cout << "I'm B" << std::endl;
    }
    
    int main() {
        std::unordered_map<std::type_index, std::function<void(void)>> mytypes;
        mytypes[typeid(A)] = doForA;
        mytypes[typeid(B)] = doForB;
    
        mytypes[typeid(A)]();
    }
    

    but both ways are wrong the virtual keyword is here for this, you MUST do like Arie said or there is mistake in your architecture

    0 讨论(0)
  • 2020-12-14 04:48

    I think you actually want to use inheritance rather than typecasing (I've never seen it before, pretty neat :) ) or type-checking.

    Small example:

    class Animal {
    public:
       virtual void makeSound() = 0; // This is a pure virtual func - no implementation in base class
    };
    
    class Dog : public Animal {
    public:
       virtual void makeSound() { std::cout << "Woof!" << std::endl; }
    }
    
    class Cat : public Animal {
    public:
       virtual void makeSound() { std::cout << "Meow!" << std::endl; }
    }
    
    int main() {
       Animal* obj = new Cat;
    
       obj->makeSound(); // This will print "Meow!"
    }
    

    In this case, I wanted to print the animal-specific sound without specific type checking. To do so, I use the virtual function "makeSound" each subclass of Animal has and overrides to print the correct output for that animal.

    Hope this is what you were aiming for.

    0 讨论(0)
  • 2020-12-14 04:57

    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!

    0 讨论(0)
  • 2020-12-14 05:02

    Some time ago I was experimenting to write a library for doing exactly that.

    You can find it here:

    https://github.com/nicola-gigante/typeswitch

    The project was quite ambitious, with a lot of planned features, and it still needs to be finished (there's also an important bug that I already know, but I don't have time to work on it anymore in this months). However, for your use case of a classic hierarchy of classes, it would work perfectly (I think).

    The basic mechanism is the same as you have posted before, but I try to extend the concept with more features:

    • You can provide a default case that is called if no other clause matches.
    • You can return a value from the clauses. The return type T is the common type of the types returned by all the clauses. If there's no default case, the return type is optional<T> instead of T.
    • You can choose between boost::optional or any implementation of std::experimental::optional from the current draft of the Library Foundamentals TS (for example, libc++ provides one).
    • You can match multiple parameters at once.
    • You can match the type contained in a boost::any object.
    • You can hook the type switch with a custom casting mechanism, to override the dynamic_cast. This is useful when using libraries that provide their own casting infrastructure, like Qt's qobject_cast, or to implement the typeswitch on your own tagged unions or anything like that.

    The library is quite finished, except for a bug that is documented in the README that makes impossible to match non-polymorphic types with static overloading resolution rules, but it's a case that would only be useful in templated generic code, and is not involved in the majority of use cases like yours. I currently don't have time to work on it to fix the bug, but I suppose it's better to post it here than leave it unused.

    0 讨论(0)
提交回复
热议问题