я читаю Современный C ++ Design. Применяются общие шаблоны программирования и проектирования. от Андрей Александреску а также глава 11 о мультиметодах имеет дело именно с проблемой, которую я пытаюсь решить. Весь исходный код из книги опубликован в библиотеке с именем Loki.
Проблема в том, что книга довольно старая (2001 г.) и имеет дело с ограничениями, которые больше не верно в C ++ 11 (например, что количество параметров шаблона не может быть переменным). Я попытался посмотреть, был ли Loki переписан с использованием C ++ 11, но последняя модификация датирована 2009 годом, и нет обновлений Домашняя страница Андрея Александреску. Однако после некоторого исследования у меня сложилось впечатление, что Loki — это что-то вроде Boost в том смысле, что он включен в стандартную библиотеку.
Имеет мультиметод идиома или некоторые ее части были приняты в C ++ 11?
Это не в стандарте, но было бы достаточно просто построить с использованием карты функциональных объектов, проиндексированных парой typeid.
Для полноты вот моя первая попытка:
#include <iostream>
#include <typeinfo>
#include <typeindex>
#include <map>
#include <functional>
#include <memory>
struct Animal {
virtual std::type_index type() const = 0;
};
template <class T> struct AnimalImpl : public Animal {
std::type_index type() const override {
return typeid(T);
}
};
struct Dog : AnimalImpl<Dog> {
};
struct Cat : AnimalImpl<Cat> {
};
struct Mouse : AnimalImpl<Mouse> {
};
using Types = std::tuple<std::type_index, std::type_index>;
using Outcome = std::function<void (Animal&, Animal&)>;
using DispatchMap = std::map<Types, Outcome>;
using namespace std;void catVDog(Animal& cat, Animal& dog) {
cout << "dog wins\n";
}
void catVMouse(Animal& cat, Animal& mouse)
{
cout << "cat wins\n";
}
DispatchMap makeOutcomes()
{
DispatchMap result;
result.emplace( make_pair( Types {typeid(Cat), typeid(Dog)}, catVDog) );
result.emplace( make_pair( Types {typeid(Dog), typeid(Cat)},
[](Animal&a1,Animal&a2) { return catVDog(a2,a1); }) );
result.emplace( make_pair( Types {typeid(Cat), typeid(Mouse)}, catVMouse) );
result.emplace( make_pair( Types {typeid(Mouse), typeid(Cat)},
[](Animal&a1,Animal&a2) { return catVMouse(a2,a1); }) );
return result;
}
const DispatchMap outcomes = makeOutcomes();
void fight(Animal& a1, Animal& a2)
{
auto it = outcomes.find(Types{ a1.type(), a2.type() });
if (it == outcomes.end()) {
cout << typeid(a1).name() << " " << typeid(a2).name() << " ";
std::cout << "no fight\n";
}
else {
it->second(a1, a2);
}
}int main()
{
unique_ptr<Animal> cat { new Cat {} };
unique_ptr<Animal> dog { new Dog {} };
unique_ptr<Animal> mouse { new Mouse {} };
fight(*cat, *dog);
fight(*cat, *mouse);
fight(*dog, *cat);
fight(*dog, *mouse);
return 0;
}
У меня есть собственная мультиметодная реализация в C ++ 11, основанная на идеях из этой книги, и я надеюсь,
это решение будет кому-то полезно.
Шаблонный класс MultiMethod параметризован с типом возвращаемого значения мультиметода и базовыми типами его
полиморфные аргументы. Этот класс представляет собой абстрактный мультиметод с интерфейсной функцией operator ().
Конкретные методы регистрируются функцией добавления шаблона. Параметры этой функции шаблона
производные типы аргументов зарегистрированного метода.
Ниже приведен исходный код класса MultiMethod:
//////////////////////////////////////////////////////////////////////////////
// MultiMethod.h
#ifndef _MULTI_METHOD_H_
#define _MULTI_METHOD_H_
#include "TypeInfo.h"#include <functional>
#include <tuple>
#include <map>
template <typename>
class MultiMethod;
template <typename Res, typename... ArgsBase>
class MultiMethod<Res(ArgsBase...)> {
template <class T>
using ArgId = TypeInfo;
using CallbackId = std::tuple<ArgId<ArgsBase>...>;
using Callback = std::function<Res(ArgsBase&...)>;
using Callbacks = std::map<CallbackId, Callback>;
Callbacks callbacks;
public:
// Method registration.
template <typename... Args, typename Fn>
void Add(Fn fn) {
callbacks[CallbackId(TypeInfo(typeid(Args))...)] = [fn](ArgsBase&... args) -> Res {
return fn(dynamic_cast<Args&>(args)...);
};
}
// Multimethod call.
template <typename... Args>
Res operator()(Args&... args) {
auto it = callbacks.find(CallbackId(TypeInfo(typeid(args))...));
if (it != callbacks.end()) {
return it->second(args...);
}
return Callback()(args...);
}
};
#endif // _MULTI_METHOD_H_
Минималистичный вспомогательный класс TypeInfo используется для идентификации конкретного метода
аргументы. Этот класс реализован в следующем исходном коде:
//////////////////////////////////////////////////////////////////////////////
// TypeInfo.h
#ifndef _TYPE_INFO_H_
#define _TYPE_INFO_H_
#include <typeinfo>
class TypeInfo {
const std::type_info& ti;
public:
TypeInfo(const std::type_info& ti) : ti(ti)
{}
friend bool operator<(const TypeInfo& t1, const TypeInfo& t2);
};
bool operator<(const TypeInfo& t1, const TypeInfo& t2);
#endif // _TYPE_INFO_H_
//////////////////////////////////////////////////////////////////////////////
// TypeInfo.cpp
#include "TypeInfo.h"
bool operator<(const TypeInfo& t1, const TypeInfo& t2)
{ return t1.ti.before(t2.ti); }
Вот пример использования класса MultiMethod:
//////////////////////////////////////////////////////////////////////////////
// main.cpp
#include "MultiMethod.h"#include <iostream>
#include <memory>
// Number base class.
class Number {
public:
virtual ~Number()
{}
};
// Integer number class.
class Integer : public Number {
int val;
public:
Integer(int v) : val {v}
{}
int Value() const
{ return val; }
};
// Real number class.
class Real : public Number {
double val;
public:
Real(double v) : val {v}
{}
double Value() const
{ return val; }
};
int main(int argc, char* argv[]) {
// Single number printing multimethod.
MultiMethod<bool(Number)> print1;
print1.Add<Real>(
[](Real& r)
{ return (std::cout << r.Value() << std::endl, true); });
print1.Add<Integer>(
[](Integer& i)
{ return (std::cout << i.Value() << std::endl, true); });
// Two numbers printing multimethod.
MultiMethod<bool(Number, Number)> print2;
print2.Add<Real, Real>(
[&print2](Real& r1, Real& r2)
{ return (std::cout << r1.Value() << " " << r2.Value() << std::endl, true); });
print2.Add<Real, Integer>(
[&print2](Real& r1, Integer& i2)
{ return (std::cout << r1.Value() << " " << i2.Value() << std::endl, true); });
print2.Add<Integer, Real>(
[&print2](Integer& i1, Real& r2)
{ return (std::cout << i1.Value() << " " << r2.Value() << std::endl, true); });
print2.Add<Integer, Integer>(
[&print2](Integer& i1, Integer& i2)
{ return (std::cout << i1.Value() << " " << i2.Value() << std::endl, true); });
// Two numbers addition multimethod.
MultiMethod<std::unique_ptr<Number>(Number, Number)> add;
add.Add<Real, Real>(
[](Real& r1, Real& r2)
{ return std::unique_ptr<Number> {new Real {r1.Value() + r2.Value()}}; });
add.Add<Integer, Integer>(
[](Integer& i1, Integer& i2)
{ return std::unique_ptr<Number> {new Integer {i1.Value() + i2.Value()}}; });
add.Add<Real, Integer>(
[&add](Real& r1, Integer& i2)
{ return add(i2, r1); });
add.Add<Integer, Real>(
[&add](Integer& i1, Real& r2) {
std::unique_ptr<Real> r1 {new Real(i1.Value())};
return add(*r1, r2);
}
);
// Multimethod call examples.
std::unique_ptr<Number> n1 {new Real {12.3}};
std::unique_ptr<Number> n2 {new Integer {4}};
print1(*n1);
print1(*n2);
print2(*n1, *n1);
print2(*n1, *n2);
print2(*n2, *n1);
print2(*n2, *n2);
print1(*add(*n1, *n1));
print1(*add(*n1, *n2));
print1(*add(*n2, *n1));
print1(*add(*n2, *n2));
return 0;
}