"类型开关"用C ++构造11

时间:2014-04-02 20:54:37

标签: c++ c++11 lambda functional-programming polymorphism

一直以来,我发现自己都是这样做的:

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

一旦我看到C ++ 11中的闭包,我想知道,这样的事情可能吗?

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

实现类型注释本来应该很容易,但我一直遇到一个问题,它无法弄清楚函数的参数,所以它无法知道尝试使用dynamic_cast,因为很难推断出lambdas的参数。花了几天的时间搜索google和SO,但终于想通了,所以我将在下面分享我的答案。

5 个答案:

答案 0 :(得分:10)

感谢ecatmur在https://stackoverflow.com/a/13359520的回答,我能够从lambda中提取签名。完整的解决方案如下所示:

// 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

以及一个示例用法:

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;
}

如果有人有任何改进,请告诉我!

答案 1 :(得分:3)

实施

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);
    }
}

用法

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

我无法在第二次访问C ++ 11编译器来尝试这一点,但我希望这个想法很有用。根据编译器能够进行多少类型推断,您可能需要或不必指定两次大小写的类型 - 我不是C ++ 11-pro就足以告诉它。

答案 2 :(得分:3)

前段时间我正在尝试编写一个库来完成这项工作。

你可以在这里找到它:

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

这个项目非常雄心勃勃,有很多计划的功能,而且还需要完成(还有一个我已经知道的重要错误,但在这几个月我没有时间再研究了它) 。但是,对于经典的类层次结构的用例,它可以完美地工作(我认为)。

基本机制与之前发布的基本机制相同,但我尝试使用更多功能扩展概念:

  • 如果没有其他子句匹配,您可以提供一个默认情况。
  • 您可以从子句中返回值。返回类型T是所有子句返回的类型的通用类型。如果没有默认大小写,则返回类型为optional<T>而不是T
  • 您可以从 Library Foundamentals TS 的当前草稿中选择boost::optionalstd::experimental::optional的任何实现(例如,libc++提供一个)。
  • 您可以一次匹配多个参数。
  • 您可以匹配boost::any对象中包含的类型。
  • 您可以使用自定义投射机制挂钩类型开关,以覆盖dynamic_cast。这在使用提供自己的转换基础结构的库(例如Qt的qobject_cast)或在您自己的标记联合或类似的事情上实现typeswitch时非常有用。

库已经完成了,除了README中记录的一个错误,它使得非多态类型与静态重载解析规则无法匹配,但它只是在模板通用中有用的情况代码,并不涉及像你这样的大多数用例。我目前没有时间去处理它来修复bug,但我想最好在这里发布它而不是让它不用。

答案 3 :(得分:2)

我认为这取决于您是否希望在编译时或运行时执行此操作。在编译时,Verdagon的答案更好,在运行时你可以做这样的事情

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)]();
}

但两种方式都错了virtual关键字在这里,你必须像Arie所说的那样或者你的架构中存在错误

答案 4 :(得分:1)

我认为你实际上想要使用继承而不是typecasing(我以前从未见过它,非常整洁:))或类型检查。

小例子:

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!"
}

在这种情况下,我想要打印特定于动物的声音而不进行特定的类型检查。为此,我使用虚拟功能&#34; makeSound&#34; Animal的每个子类都有并覆盖以打印该动物的正确输出。

希望这是你的目标。