Сделали ли мультиметоды Loki C++11?

Я читаю современные шаблоны программирования и проектирования C++ Design, применяемые Андреем Александреску, и глава 11 о мультиметодах посвящена именно той проблеме, которую я пытаюсь решить. Весь исходный код из книги опубликован в библиотеке под названием Loki.

Проблема в том, что книга довольно старая (2001 г.) и имеет дело с ограничениями, которые больше не действуют в C++ 11 (например, количество параметров шаблона не может быть переменным). Я попытался посмотреть, был ли Loki переписан с использованием C++ 11, но последняя модификация датирована 2009 годом, и на домашней странице Андрея Александреску нет обновлений. Однако после некоторого исследования у меня сложилось впечатление, что Loki - это что-то вроде Boost в том смысле, что он включен в стандартную библиотеку.

Была ли мультиметодная идиома или некоторые ее части приняты в C++11?

2 ответа

Решение

Это не в стандарте, но было бы достаточно просто построить с использованием карты функциональных объектов, проиндексированных парой 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;
}
Другие вопросы по тегам