{"id":21033740,"url":"https://github.com/jll63/yomm2","last_synced_at":"2025-04-06T04:16:53.174Z","repository":{"id":37275576,"uuid":"103060181","full_name":"jll63/yomm2","owner":"jll63","description":"Fast, orthogonal, open multi-methods. Solve the Expression Problem in C++17.","archived":false,"fork":false,"pushed_at":"2024-04-13T22:57:10.000Z","size":956,"stargazers_count":319,"open_issues_count":0,"forks_count":16,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-04-14T05:46:07.426Z","etag":null,"topics":["cpp","cpp17","expression-problem","multi-methods","multiple-dispatch","open-methods","polymorphism"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jll63.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2017-09-10T20:39:12.000Z","updated_at":"2024-04-15T13:04:07.884Z","dependencies_parsed_at":"2023-02-18T17:30:51.855Z","dependency_job_id":"d27c7b33-9b99-4184-8de3-f92111273f38","html_url":"https://github.com/jll63/yomm2","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jll63%2Fyomm2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jll63%2Fyomm2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jll63%2Fyomm2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jll63%2Fyomm2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jll63","download_url":"https://codeload.github.com/jll63/yomm2/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247430966,"owners_count":20937875,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp17","expression-problem","multi-methods","multiple-dispatch","open-methods","polymorphism"],"created_at":"2024-11-19T12:59:23.869Z","updated_at":"2025-04-06T04:16:53.147Z","avatar_url":"https://github.com/jll63.png","language":"C++","funding_links":[],"categories":["Frameworks","C++"],"sub_categories":[],"readme":"\n# YOMM2\n\n\n[![CI](https://github.com/jll63/yomm2/actions/workflows/main.yml/badge.svg)](https://github.com/jll63/yomm2/actions/workflows/main.yml)\n[![ConanCenter package](https://repology.org/badge/version-for-repo/conancenter/yomm2.svg)](https://repology.org/project/yomm2/versions)\n[![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/yomm2.svg)](https://repology.org/project/yomm2/versions)\n---\n\nThis library implements fast, open, multi-methods for C++17. It is strongly\ninspired by the papers by Peter Pirkelbauer, Yuriy Solodkyy, and Bjarne\nStroustrup.\n\n- [YOMM2](#yomm2)\n  - [TL;DR](#tldr)\n  - [Open Methods in a Nutshell](#open-methods-in-a-nutshell)\n    - [Cross-cutting Concerns and the Expression Problem](#cross-cutting-concerns-and-the-expression-problem)\n    - [Multiple Dispatch](#multiple-dispatch)\n  - [Performance](#performance)\n  - [Installation](#installation)\n  - [Going Further](#going-further)\n  - [Roadmap](#roadmap)\n\n## TL;DR\n\nIf you are familiar with the concept of open multi-methods, or if you prefer\nto learn by reading code, go directly to [the\nsynopsis](examples/synopsis.cpp). The [documentation is\nhere](https://jll63.github.io/yomm2)\n\n## Open Methods in a Nutshell\n\n### Cross-cutting Concerns and the Expression Problem\n\nYou have a matrix math library. It deals with all sort of matrices: dense,\ndiagonal, tri-diagonal, etc. Each matrix subtype has a corresponding class in a\nhierarchy rooted in Matrix.\n\nNow you would like to render Matrix objects as JSON strings. The representation\nwill vary depending on the exact type of the object; for example, if a matrix\nis a DiagonalMatrix, you only need to store the diagonal - the other elements\nare all zeroes.\n\nThis is an example of a [\"cross-cutting\nconcern\"](http://wiki.c2.com/?CrossCuttingConcern). How do you do it?\n\nIt turns out that OOP doesn't offer a good solution to this.\n\nYou can stick a pure virtual `to_json` function in the `Matrix` base class and\noverride it in the subclasses. It is an easy solution but it has severe\ndrawbacks. It requires you to change the Matrix class and its subclasses, and\nrecompile the library. And now all the applications that use it will contain\nthe `to_json` functions even if they don't need them, because of the way\nvirtual functions are implemented.\n\nOr you may resort on a \"type switch\": have the application test for each\ncategory and generate the JSON accordingly. This is tedious, error prone and,\nabove all, not extensible. Adding a new matrix subclass requires updating all\nthe type switches. The Visitor pattern also suffers from this flaw.\n\nWouldn't it be nice if you could add behavior to existing types, just as easily\nand unintrusively as you can extend existing class hierarchies via derivation?\nWhat if you could solve the so-called [Expression\nProblem](http://wiki.c2.com/?ExpressionProblem):\n\n```\nexisting behaviors += new types\nexisting types += new behaviors\n```\n\nThis is exactly what Open Methods are all about: solving the Expression\nProblem.\n\nLet's look at an example.\n\n\n```c++\n// -----------------------------------------------------------------------------\n// library code\n\nstruct matrix {\n    virtual ~matrix() {\n    }\n    // ...\n};\n\nstruct dense_matrix : matrix { /* ... */\n};\nstruct diagonal_matrix : matrix { /* ... */\n};\n\n// -----------------------------------------------------------------------------\n// application code\n\n#include \u003cmemory\u003e\n#include \u003cyorel/yomm2/keywords.hpp\u003e\n\nregister_classes(matrix, dense_matrix, diagonal_matrix);\n\ndeclare_method(std::string, to_json, (virtual_\u003cconst matrix\u0026\u003e));\n\ndefine_method(std::string, to_json, (const dense_matrix\u0026 m)) {\n    return \"json for dense matrix...\";\n}\n\ndefine_method(std::string, to_json, (const diagonal_matrix\u0026 m)) {\n    return \"json for diagonal matrix...\";\n}\n\nint main() {\n    yorel::yomm2::update();\n\n    const matrix\u0026 a = dense_matrix();\n    const matrix\u0026 b = diagonal_matrix();\n\n    std::cout \u003c\u003c to_json(a) \u003c\u003c \"\\n\"; // json for dense matrix\n    std::cout \u003c\u003c to_json(b) \u003c\u003c \"\\n\"; // json for diagonal matrix\n\n    return 0;\n}\n```\n\n\n`\u003cyorel/yomm2/keywords.hpp\u003e` is the library's main entry point. It declares a\nset of macros, and injects a single name, [`virtual_`](/yomm2/reference/virtual_.html), in the global\nnamespace. The purpose of the header is to make it look as if open methods\nare part of the language.\n\n[`register_classes`](/yomm2/reference/use_classes.html) informs the library of the existence of the classes, and\ntheir inheritance relationships. Any class that can appear in a method call\nneeds to be registered, even if it is not directly referenced by a method.\n\n`declare_method` declares an open method called `to_json`, which takes one\nvirtual argument of type `const matrix\u0026` and returns a std::string. The\n`virtual_\u003c\u003e` decorator specifies that the argument must be taken into account\nto select the appropriate specialization. In essence, this is the same thing\nas having a `virtual std::string to_json() const` inside class Matrix -\nexcept that the virtual function lives outside of any classes, and you can\nadd as many as you want without changing the classes. NOTE: DO NOT specify\nargument names, i.e. `virtual_\u003cconst matrix\u0026\u003e arg` is _not permitted_.\n\n`define_method` defines two implementations for the `to_json` method: one for\ndense matrices, and one for diagonal matrices.\n\n`yorel::yomm2::update()` creates the dispatch tables; it must be called\nbefore any method is called, and after dynamically loading and unloading\nshared libraries.\n\nThe example can be compiled (from the root of the repository) with:\n```shell\nclang++- -I include -std=c++17 tutorials/README.cpp -o example\n```\n\n### Multiple Dispatch\n\nMethods can have more than one virtual argument. This is handy in certain\nsituations, for example to implement binary operations on matrices:\n\n```c++\n// -----------------------------------------------------------------------------\n// matrix * matrix\n\ndeclare_method(\n    std::shared_ptr\u003cconst matrix\u003e,\n    times, (virtual_\u003cconst matrix\u0026\u003e, virtual_\u003cconst matrix\u0026\u003e));\n\n// catch-all matrix * matrix -\u003e dense_matrix\ndefine_method(\n    std::shared_ptr\u003cconst matrix\u003e,\n    times, (const matrix\u0026 a, const matrix\u0026 b)) {\n    return std::make_shared\u003cdense_matrix\u003e();\n}\n\n// diagonal_matrix * diagonal_matrix -\u003e diagonal_matrix\ndefine_method(\n    std::shared_ptr\u003cconst matrix\u003e,\n    times, (const diagonal_matrix\u0026 a, const diagonal_matrix\u0026 b)) {\n    return std::make_shared\u003cdiagonal_matrix\u003e();\n}\n```\n\n## Performance\n\nOpen methods are almost as fast as ordinary virtual member functions once you\nturn on optimization (-O2). With both clang and gcc, dispatching a call to a\nmethod with one virtual argument takes 15-30% more time than calling the\nequivalent virtual member function (unless the call goes through a virtual base,\nwhich requires a dynamic cast). It does not involve branching or looping, only a\nfew memory reads (which the CPU can be parallelize), a multiplication, a bit\nshift, a final memory read, then an indirect call. If the body of the method\ndoes any amount of work, the difference is unnoticeable.\n\n[`virtual_ptr`](https://jll63.github.io/yomm2/reference/virtual_ptr.md), a fat\npointer class, can be used to make method dispatch even faster - three\ninstructions and two memory reads.\n\n[Examples](ce/README.md) are available on Compiler Explorer.\n\n## Installation\n\nYOMM2 is available on both major package managers. This is the easiest way of\nintegrating it in your project, along with its dependencies. See [the vcpkg\nexample](examples/vcpkg) and [the Conan2 example](examples/conan).\n\nYOMM2 can also be built and installed from the sources, using straight `cmake`.\n\nFirst clone the repository:\n\n```\ngit clone https://github.com/jll63/yomm2.git\n```\n\nRun cmake:\n\n```\ncmake -S yomm2 -B build.yomm2\ncmake --build build.yomm2\n```\n\nIf you want to run the tests, specify it when running `cmake`:\n\n```\ncmake -S yomm2 -B build.yomm2 -DYOMM2_ENABLE_TESTS=1\ncmake --build build.yomm2\nctest --test-dir build.yomm2\n```\n\nYOMM2 uses the following Boost libraries:\n* Mp11, Preprocessor, DynamicBitset: included by YOMM2 headers\n* Test: only used to run the test suite\n\nIf you want to run the benchmarks (and in this case you really want a release\nbuild):\n\n```\ncmake -S yomm2 -B build.yomm2 -DYOMM2_ENABLE_TESTS=1 -DYOMM2_ENABLE_BENCHMARKS=1 -DCMAKE_BUILD_TYPE=Release\n./build.yomm2/tests/benchmarks\n```\nThe benchmarks use the [Google benchmark](https://github.com/google/benchmark)\nlibrary.\n\nIf you like YOMM2, and you want to install it, either system-wide:\n\n```\nsudo cmake --install build.yomm2\n```\n\n...or to a specific directory:\n\n```\nDESTDIR=/path/to/my/libs cmake --install build.yomm2\n```\n\nThis will install the headers and a CMake package configuration. By default,\nYOMM2 is installed as a headers only library. The examples can be compiled like\nthis (after installation):\n\n```\nclang++ -std=c++17 -O3 examples/synopsis.cpp -o synopsis\n```\n\nOr directly from the repository (i.e. without installing):\n\n```\nclang++ -std=c++17 -O3 -Iinclude examples/synopsis.cpp -o synopsis\n```\n\nThe YOMM2 runtime - responsible for building the dispatch tables - adds ~75K to\nthe image, or ~64K after stripping.\n\nThe runtime can also be built and installed as a shared library, by adding\n-DYOMM2_SHARED=1 to the `cmake` command line.\n\nA CMake package configuration is also installed. If the install location is in\n`CMAKE_PREFIX_PATH`, you can use `find_package(YOMM2)` to locate YOMM2, then\n`target_link_libraries(\u003cyour_target\u003e YOMM2::yomm2)` to add the necessary include\npaths and the library. See [this example](examples/cmakeyomm2).\n\nMake sure to add the install location to `CMAKE_PREFIX_PATH` so that you can use\n`find_package(YOMM2)` from your including project. For linking, the use\n`target_link_library(\u003cyour_target\u003e YOMM2::yomm2)`. This will automatically add\nthe necessary include directories, so this should be all you need to do to link\nto yomm2.\n\n## Going Further\n\nThe documentation is [here](https://jll63.github.io/yomm2). Since version 1.3.0,\nsome of the internals are documented, which make it possible to use the library\nwithout using macros - see [the API\ntutorial](https://jll63.github.io/yomm2/tutorials/api.html).\n\nYOMM2 has *experimental* support for writing templatized methods and definitions\n- see [the templates\n  tutorial](https://jll63.github.io/yomm2/tutorials/templates_tutorial.html).\n\nThe library comes with a series of examples:\n\n* [The complete `matrix` example](examples/matrix.cpp)\n\n* [The Asteroids example used in Wikipedia's article on Multiple\n  Dispatch](examples/asteroids.cpp)\n\n* [Process an AST sans clumsy Visitor](examples/accept_no_visitors.cpp)\n\n* [Adventure: a 3-method example](examples/adventure.cpp)\n\n* [friendship: an example with namespaces, method containers and friend\n  declarations](examples/containers)\n\nI presented the library at CppCon 2018. Here are [the video\nrecording](https://www.youtube.com/watch?v=xkxo0lah51s) and [the\nslides](https://jll63.github.io/yomm2/slides/).\n\n## Roadmap\n\nYOMM2 has been stable (in the sense of being backward-compatible) for many\nyears, but it is still evolving. Here are the items on which I intend to work in\nthe future. No promises, no time table.\n\n* Dispatch on `std::any`.\n* Static offsets (i.e. set at compile time).\n* Static linking of dispatch data.\n* *Minimal* perfect hash tables as an option.\n* Multi-threaded hash search.\n* Get closer to Stroustrup et al's papers (version 2.0):\n  * use compatible return types for disambiguation\n  * move support for `std::shared_ptr` and `unique_ptr` to an optional header\n\nIf you have ideas, comments, suggestions...get in touch! If you use YOMM2, I\nwould appreciate it if you take the time to send me a description of your use\ncase(s), and links to the project(s), if they are publicly available.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjll63%2Fyomm2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjll63%2Fyomm2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjll63%2Fyomm2/lists"}