Конструкция "type-switch" в С++ 11
Я все время делаю что-то вроде этого:
Animal *animal = ...
if (Cat *cat = dynamic_cast<Cat *>(animal)) {
...
}
else if (Dog *dog = dynamic_cast<Dog *>(animal)) {
...
}
else { assert(false); }
Как только я вижу закрытие на С++ 11, интересно, что-то вроде этого возможно?
Animal *animal = ...
typecase(animal,
[](Cat *cat) {
...
},
[](Dog *dog) {
...
});
Реализация typecase должна быть простой, но я все время сталкиваюсь с проблемой, когда он не может определить аргумент функции, поэтому он не может знать, к чему пытаться использовать dynamic_cast, потому что трудно выводить параметры lambdas. Взял несколько дней на поиск google и SO, но, наконец, понял, поэтому я поделился своим ответом ниже.
Ответы
Ответ 1
Благодаря ответу от ecatmur на fooobar.com/questions/110296/... мне удалось извлечь подпись из лямбды. Полное решение выглядит следующим образом:
// Begin ecatmur 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 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;
}
Если у кого есть какие-либо улучшения, скажите, пожалуйста!
Ответ 2
Некоторое время назад я экспериментировал, чтобы написать библиотеку для выполнения именно этого.
Вы можете найти его здесь:
https://github.com/nicola-gigante/typeswitch
Проект был довольно амбициозным, с множеством запланированных функций, и его все еще нужно закончить (также есть важная ошибка, о которой я уже знаю, но у меня нет времени работать над этим больше в этом месяце), Однако, для вашего случая использования классической иерархии классов, он будет работать отлично (я думаю).
Основной механизм такой же, как и ранее, но я пытаюсь расширить концепцию с помощью дополнительных функций:
- Вы можете предоставить случай по умолчанию, который вызывается, если никакое другое предложение не соответствует.
- Вы можете вернуть значение из предложений. Тип возврата
T
- это общий тип типов, возвращаемых всеми предложениями. Если нет случая по умолчанию, тип возврата optional<T>
вместо T
.
- Вы можете выбрать
boost::optional
или любую реализацию std::experimental::optional
из текущего черновика TS (например, libc++
).
- Вы можете сразу сопоставить несколько параметров.
- Вы можете сопоставить тип, содержащийся в объекте
boost::any
.
- Вы можете подключить переключатель типа с помощью настраиваемого механизма литья, чтобы переопределить
dynamic_cast
. Это полезно при использовании библиотек, которые предоставляют свою собственную инфраструктуру литья, например Qt qobject_cast
, или для реализации типаwitch в ваших собственных тегированных объединениях или что-то в этом роде.
Библиотека полностью завершена, за исключением ошибки, которая задокументирована в README
, что делает невозможным сопоставление неполиморфных типов со статическими правилами разрешения перегрузки, но это случай, который был бы полезен только в шаблоном общем коде, и не участвует в большинстве случаев использования, подобных вашим. В настоящее время у меня нет времени работать над этим, чтобы исправить ошибку, но я полагаю, что лучше разместить ее здесь, чем оставить ее неиспользованной.
Ответ 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)
{
...
});
У меня нет доступа к компилятору С++ 11, чтобы попробовать это, но я надеюсь, что эта идея полезна. В зависимости от того, сколько экземпляров вывода, на которые способен компилятор, вам может потребоваться или не обязательно указывать тип case дважды - я не С++ 11-pro достаточно, чтобы сказать, глядя на него.
Ответ 4
Я думаю, это зависит от того, хотите ли вы сделать это во время компиляции или во время выполнения. Для времени компиляции ответ Вердагона лучше, во время выполнения вы можете сделать что-то вроде этого
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
здесь для этого, вы ДОЛЖНЫ делать, как сказал Ари, или в вашей архитектуре есть ошибка.
Ответ 5
Я думаю, вы действительно хотите использовать наследование, а не 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!"
}
В этом случае я хотел напечатать звук, специфичный для животных, без специальной проверки типа. Для этого я использую виртуальную функцию "makeSound" для каждого подкласса Animal имеет и отменяет печать правильного вывода для этого животного.
Надеюсь, это то, к чему вы стремились.