Skip to content

Instantly share code, notes, and snippets.

@hutorny
Last active January 16, 2021 09:02
Show Gist options
  • Save hutorny/2a87190d55a28d6c69c90c5a81643d85 to your computer and use it in GitHub Desktop.
Save hutorny/2a87190d55a28d6c69c90c5a81643d85 to your computer and use it in GitHub Desktop.
Multimethods for C++, example 2, shapes multimethod
#include <iostream>
#include <stdexcept>
#define TRACE() \
std::cout << __PRETTY_FUNCTION__ << std::endl;
namespace details {
template<class Class>
struct is_virtual_parameter {
static constexpr bool value =
std::is_polymorphic_v<std::remove_reference_t<Class>> and
std::is_reference_v<Class>;
};
template<class Class>
constexpr auto is_virtual_parameter_v = is_virtual_parameter<Class>::value;
template<typename Parameter, typename Argument>
struct is_assignable_parameter {
static constexpr bool value = std::is_assignable_v<std::remove_const_t<Parameter>&, Argument>;
};
template<typename Parameter, typename Argument>
constexpr auto is_assignable_parameter_v = is_assignable_parameter<Parameter, Argument>::value;
template<class Class>
constexpr auto has_instanceof(int) noexcept -> decltype(&Class::template instanceof<Class> ,true) { return true; }
template<class Class>
constexpr auto has_instanceof(long) noexcept { return false; }
template<class Parameter>
struct expected {
using parameter_type = std::remove_reference_t<std::remove_cv_t<Parameter>>;
template<class Argument>
static constexpr bool matches(Argument&& argument) noexcept {
if constexpr(has_instanceof<parameter_type>(0)) {
return argument.template instanceof<parameter_type>();
} else {
#if __cpp_rtti >= 199711
return typeid(Parameter) == typeid(argument);
#else
static_assert(not is_virtual_parameter_v<parameter_type>, "No class info available");
return is_assignable_parameter_v<Parameter, Argument>;
#endif
}
}
};
template<auto Method>
struct entry;
template<class Return, class ... Parameter, Return (*Function)(Parameter...)>
struct entry<Function> {
template<class ... Argument>
static constexpr bool matches(const Argument& ... argument) noexcept {
return (expected<Parameter>::matches(argument) and ...);
}
template<class ... Argument>
static Return call(Argument& ... argument) noexcept(noexcept(Function)) {
return (*Function)((Parameter)(argument)...);
}
};
template<class Target, class Return, class ... Parameter, Return (Target::*Method)(Parameter...)>
struct entry<Method> {
template<class Object, class ... Argument>
static constexpr bool matches(Object& obj, Argument& ... argument) noexcept {
return expected<Target>::matches(obj) and (expected<Parameter>::matches(argument) and ...);
}
template<class Object, class ... Argument>
static Return call(Object& target, Argument& ... argument) noexcept(noexcept(Method)) {
return ((Target&)(target).*Method)((Parameter&)(argument)...);
}
};
template<class Target, class Return, class ... Parameter, Return (Target::*Method)(Parameter...)const>
struct entry<Method> {
template<class Object, class ... Argument>
static constexpr bool matches(const Object& obj, Argument& ... argument) {
return expected<Target>::matches(obj) and (expected<Parameter>::matches(argument) and ...);
}
template<class Object, class ... Argument>
static Return call(const Object& target, Argument& ... argument) {
return ((const Target&)(target).*Method)((Parameter&)(argument)...);
}
};
} //namespace details
template<typename ReturnType, auto ... Entries>
struct multimethod {
template<class ... Arguments>
static auto dispatch(Arguments& ... arguments) {
ReturnType value;
if (((details::entry<Entries>::matches(arguments...)
and ((value = details::entry<Entries>::call(arguments...)),true)) or ...))
return value;
else
throw std::logic_error("Dispatcher failure");
}
template<class Target, class ... Arguments>
static auto call(Target& target,Arguments& ... arguments) {
ReturnType value;
if (((details::entry<Entries>::matches(target, arguments...)
and ((value = details::entry<Entries>::call(target, arguments...)),true)) or ...))
return value;
else
throw std::logic_error("Dispatcher failure");
}
template<class Target, class ... Arguments>
static auto call(const Target& target,Arguments& ... arguments) {
ReturnType value;
if (((details::entry<Entries>::matches(target, arguments...)
and ((value = details::entry<Entries>::call(target, arguments...)),true)) or ...))
return value;
else
throw std::logic_error("Dispatcher failure");
}
};
namespace fnv1c {
/* FNV-1b hash function with extra rotation
* https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
*/
template<typename T = std::size_t>
inline T hash(const char* str, std::size_t size) noexcept;
template<typename T>
inline constexpr T ror(T x, unsigned N) noexcept {
return (x << (sizeof(T)*8 - N)) | ((x) >> (N));
}
template<>
inline constexpr uint32_t hash<uint32_t>(const char* str, std::size_t size) noexcept {
constexpr uint32_t val32 = 0x811c9dc5;
constexpr uint32_t prime32 = 16777619;
uint32_t result = val32;
while(size--) {
result ^= uint32_t(*str);
result = ror(result, (8+(0xF&(*str++)))); /* added rotation 8-23 bits */
result *= prime32;
}
return result;
}
template<>
inline constexpr uint64_t hash<uint64_t>(const char* str, std::size_t size) noexcept {
constexpr uint64_t val64 = 0xcbf29ce484222325;
constexpr uint64_t prime64 = 1099511628211ULL;
uint64_t result = val64;
while(size--) {
result ^= uint64_t(*str);
result = ror(result, (8+(*str++)%48)); /* added rotation 8-55 bits */
result *= prime64;
}
return result;
}
template<typename T = std::size_t>
inline constexpr T hash(const std::string_view str) noexcept {
return hash<T>(str.data(), str.size());
}
} // namespace fnv1c
template<typename Result, typename ... Args>
struct resolve {
template<class Class>
constexpr auto operator()(Result (Class::*m)(Args ...)) const noexcept {
return m;
}
template<class Class>
constexpr auto operator()(Result (Class::*m)(Args ...)const) const noexcept {
return m;
}
constexpr auto operator()(Result (*m)(Args ...)) const noexcept {
return m;
}
};
namespace shapes {
template<class Class>
constexpr auto class_hash() noexcept {
return fnv1c::hash<>(std::string_view(__PRETTY_FUNCTION__));
}
#if __cpp_rtti >= 199711
using class_info = std::type_info;
template<class Class>
const class_info& classinfo() noexcept {
return typeid(Class);
}
template<class Class>
const class_info& classinfo(const Class&) noexcept {
return typeid(Class);
}
#else
using class_info = size_t;
template<class Class>
class_info classinfo() noexcept {
return Class::classid;
}
template<class Class>
class_info classinfo(const Class&) noexcept {
return Class::classid;
}
#endif
class Shape {
public:
static constexpr auto classid = class_hash<Shape>();
template<class Expected>
bool instanceof() const noexcept {
return instance_of(classinfo<Expected>());
}
Shape* intersect(const Shape&) const;
Shape* join(const Shape&) const;
virtual ~Shape() {}
protected:
virtual bool instance_of(const class_info& expected) const noexcept {
return classinfo(*this) == expected;
}
};
class Circle;
class Rect;
class Rect : public Shape {
public:
static constexpr auto classid = class_hash<Rect>();
Shape* intersect(const Circle&) const { TRACE(); return nullptr; }
Rect* intersect(const Rect&)const { TRACE(); return nullptr; }
Shape* join(const Circle&) { TRACE(); return nullptr; }
Shape* join(const Rect&) { TRACE(); return nullptr; }
protected:
bool instance_of(const class_info& expected) const noexcept override {
return classinfo(*this) == expected or Shape::instance_of(expected);
}
};
class Circle : public Shape {
public:
static constexpr auto classid = class_hash<Circle>();
Shape* intersect(const Circle&) const { TRACE(); return nullptr; }
Shape* intersect(const Rect&) const { TRACE(); return nullptr; }
Shape* join(const Circle&) { TRACE(); return nullptr; }
Shape* join(const Rect&) { TRACE(); return nullptr; }
protected:
bool instance_of(const class_info& expected) const noexcept override {
return classinfo(*this) == expected or Shape::instance_of(expected);
}
};
struct Square : public Rect {
static constexpr auto classid = class_hash<Square>();
protected:
bool instance_of(const class_info& expected) const noexcept override {
return classinfo(*this) == expected or Rect::instance_of(expected);
}
};
inline Shape* Shape::intersect(const Shape& shape) const {
return multimethod<Shape*,
resolve<Shape*, const Circle&>{}(&Rect::intersect),
resolve<Rect*, const Rect&>{}(&Rect::intersect),
resolve<Shape*, const Circle&>{}(&Circle::intersect),
resolve<Shape*, const Rect&>{}(&Circle::intersect)>::call(*this, shape);
}
inline Shape* Shape::join(const Shape& shape) const {
return multimethod<Shape*,
resolve<Shape*, const Circle&>{}(&Rect::join),
resolve<Shape*, const Rect&>{}(&Rect::join),
resolve<Shape*, const Circle&>{}(&Circle::join),
resolve<Shape*, const Rect&>{}(&Circle::join)>::call(*this, shape);
}
inline void test(const Shape& a, const Shape& b) {
a.intersect(b);
b.join(a);
}
inline void test() {
Circle circle{};
Rect rect{};
Square square{};
test(rect, circle);
test(circle, square);
test(circle, circle);
test(square, rect);
}
} // namespace c
int main() {
shapes::test();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment