Last active
January 16, 2021 09:02
-
-
Save hutorny/2a87190d55a28d6c69c90c5a81643d85 to your computer and use it in GitHub Desktop.
Multimethods for C++, example 2, shapes multimethod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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