Did the Loki multimethods make it into C++11?
Asked Answered
I

2

7

I am reading Modern C++ Design Generic Programming and Design Patterns Applied by Andrei Alexandrescu and chapter 11 on multimethods deals exactly with the problem I am trying to solve. All source code from the book is published in a library named Loki.

The problem is that the book is fairly old (2001) and deals with restrictions that are no longer true in C++11 (e.g. that the number of template parameters cannot be variable). I tried to look up if Loki has been rewritten using C++11, but the last modification is dated 2009 and there are no updates on Andrei Alexandrescu's home page. However, after doing some research I have the impression that Loki is something like Boost in the sense that it gets incorporated into the standard library.

Has the multimethod idiom or some of its parts been adopted into C++11?

Insinuating answered 28/4, 2014 at 8:52 Comment(2)
I've done that which may help : ideone.com/8VxALsAshy
Also see stroustrup.com/OpenPatternMatching.pdfPicro
G
5

It's not in the standard but would be reasonably easy to build using a map of function objects indexed by a pair of typeids.

For completeness, here's my first attempt:

#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;
}
Gawain answered 28/4, 2014 at 9:8 Comment(3)
You'll need compile time reflection to handle inheritance reasonably.Rationalism
it's already available in meta_functions like std::is_base_of. The example I have given is a very simple one. Of course it could be expanded.Gawain
is_base_of only gets you part way, and far from the goal, unless I am missing something.Rationalism
G
1

I have my own multimethod implementation in C++11 based on ideas from this book and I hope this solution will be useful to someone.

MultiMethod template class is parametrized with multimethod return value type and base types of its polymorphic arguments. This class represents abstract multimethod with operator() interface function. Concrete methods are registered by Add template function. This function template parameters are derived types of registered method arguments.

The following is MultiMethod class source code:

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

Minimalistic helper class TypeInfo is used for identification of concrete method arguments. This class is implemented in following source code:

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

Here is the example of using MultiMethod class:

//////////////////////////////////////////////////////////////////////////////
// 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;
}
Goatish answered 3/10, 2017 at 18:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.