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.

有帮助吗?

解决方案

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!

其他提示

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.

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.

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

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.

A little trick, how to create static information about types so that you can compare them quickly with one switch statically or dynamically.

#include <iostream>
#include <vector>

struct Base {
    int m_ClassID;
    Base(int _id) : m_ClassID(_id) {} // dynamic classID
};

template<int N>
struct Base_ : Base {
    static constexpr int g_ClassID = N; // static classID
    Base_() : Base(N) {}
};

struct A : Base_<2222> // define ClassID
{};

struct B  : Base_<3333> // define ClassID
{};

void DoForAnyBase(Base* pBase) {
    switch(pBase->m_ClassID) {
    case A::g_ClassID:
        printf("I'm A");
        break;
    case B::g_ClassID:
        printf("I'm B");
        break;
    }
}

int main() {
    std::vector< Base* > aTypes;
    aTypes.push_back( new A() );
    aTypes.push_back( new B() );

    for( Base* pCur : aTypes) {
        DoForAnyBase(pCur);
        delete pCur;
    }
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top