C++11中的“类型开关”构造

anauzrmj  于 2023-02-01  发布在  其他
关注(0)|答案(6)|浏览(165)

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

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

实现typecase应该很容易,但我一直遇到一个问题,它不能弄清楚函数的参数,所以它不知道尝试dynamic_cast到什么,因为很难推导出lambdas的参数.花了几天时间搜索谷歌和SO,但最终找到了,所以我会在下面分享我的答案.

fykwrbwg

fykwrbwg1#

多亏了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;
}

如果谁有什么改进,请告诉我!

b0zn9rqh

b0zn9rqh2#

实施

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

我现在还没有C11编译器来尝试这个方法,但我希望这个想法是有用的。根据编译器的类型推断能力,你可能需要也可能不需要两次指定case的类型--我对C11-pro的了解还不够,看一看就知道了。

js4nwp54

js4nwp543#

前一段时间,我正在尝试编写一个库来完成这一任务。
你可以在这里找到它:
https://github.com/nicola-gigante/typeswitch
这个项目是相当雄心勃勃的,有很多计划中的特性,它仍然需要完成(还有一个重要的bug,我已经知道了,但是我在这几个月里没有时间再处理它了)。然而,对于您的经典类层次结构的用例,它将完美地工作(我认为)。
基本机制与您之前发布的相同,但我尝试用更多的特性来扩展这个概念:

  • 您可以提供一个默认的case,在没有其他子句匹配时调用该case。
  • 可以从子句返回值。返回类型T是所有子句返回的类型的公共类型。如果没有默认大小写,则返回类型为optional<T>而不是T
  • 您可以选择boost::optional或 * Library Foundentals TS * 当前草案中std::experimental::optional的任何实现(例如,libc++提供了一个)。
  • 可以一次匹配多个参数。
  • 可以匹配boost::any对象中包含的类型。
  • 你可以用一个定制的强制转换机制来绑定类型开关,以覆盖dynamic_cast,这在使用提供了自己的强制转换基础设施的库时很有用,比如Qt的qobject_cast,或者在你自己的标记联合体上实现类型开关时也很有用。

这个库已经完成了,除了README中记录的一个bug,这个bug使得不可能将非多态类型与静态重载解决规则相匹配,但是这种情况只在模板化的泛型代码中有用,并且不涉及像您这样的大多数用例。不过我想把它寄到这里总比不使用好。

mfpqipee

mfpqipee4#

我认为这取决于你想在编译时还是运行时做这件事。对于编译时,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所说的那样做,否则您的体系结构中存在错误

syqv5f0l

syqv5f0l5#

我认为您实际上希望使用继承,而不是类型大小写(我以前从未见过,非常简洁:)或类型检查。
小例子:

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

在本例中,我想打印动物特有的声音而不进行特定的类型检查,为此,我使用了Animal的每个子类所具有的虚函数"makeSound"和重写来打印该动物的正确输出。
希望这是你想要的。

q5iwbnjs

q5iwbnjs6#

一个小技巧,如何创建关于类型的静态信息,以便可以用一个开关静态或动态地快速比较它们。

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

相关问题