{"id":13424491,"url":"https://github.com/codeinred/recursive-variant","last_synced_at":"2025-03-15T18:35:13.609Z","repository":{"id":174744103,"uuid":"414756796","full_name":"codeinred/recursive-variant","owner":"codeinred","description":"Recursive Variant: A simple library for Recursive Variant Types","archived":false,"fork":false,"pushed_at":"2021-10-22T04:17:44.000Z","size":556,"stargazers_count":82,"open_issues_count":1,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-26T23:55:19.142Z","etag":null,"topics":["cpp","cpp20","functional-programming","header-only","header-only-library","recursion","recursive-types","sum-types","variant","variants"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codeinred.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":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-10-07T21:01:55.000Z","updated_at":"2024-10-09T09:24:07.000Z","dependencies_parsed_at":"2023-08-29T16:47:39.236Z","dependency_job_id":null,"html_url":"https://github.com/codeinred/recursive-variant","commit_stats":null,"previous_names":["codeinred/recursive-variant"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeinred%2Frecursive-variant","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeinred%2Frecursive-variant/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeinred%2Frecursive-variant/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeinred%2Frecursive-variant/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codeinred","download_url":"https://codeload.github.com/codeinred/recursive-variant/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243775860,"owners_count":20346279,"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","cpp20","functional-programming","header-only","header-only-library","recursion","recursive-types","sum-types","variant","variants"],"created_at":"2024-07-31T00:00:55.135Z","updated_at":"2025-03-15T18:35:08.570Z","avatar_url":"https://github.com/codeinred.png","language":"C++","readme":"# `rva::variant` — Recursive Sum Types for C++\n\n_Provided by the Recursive Variant Authority. We stand united in opposition to\nthe [TVA](https://youtu.be/nW948Va-l10). May your variants never be pruned._\n\nVariants are exceedingly useful in C++, but they suffer from a singular and\nfundamental shortcoming: unlike sum types in many other languages, there's no\nmechanism to define recursive variants in C++.\n\nThe Recursive Variant Authority provides a solution in the form of\n`rva::variant`, which allows you to write arbitrary recursive sum types with\nfunctionality identical to that provided by `std::variant`.\n\n![](.github/recursive-variant-authority.png)\n\n## Understanding Recursive Variants\n\nTo understand what a recursive variant is, and why it's useful, consider\nsomething like a value in json. It can be any of the following:\n\n- A null value\n- A string\n- A number (usually represented as a double)\n- A boolean value (either true or false),\n- An object (aka, a map between strings and json values)\n- An array of values.\n\nIn a language like haskell, we could copy that definition pretty much verbatim\n(accounting for syntax, of course):\n\n```hs\ndata JsonValue = JsonNull\n               | JsonBoolean Bool\n               | JsonNumber Double\n               | JsonStr String\n               | JsonObject (Map String JsonValue)\n               | JsonArray [JsonValue]\n```\n\nIt's important to note that `JsonValue` is being used in it's own definition:\nit's a recursive type.\n\n## Recursive types in C++\n\nWhile this may seem a bit strange at first, recursive types aren't all that\nunusual. C++ has them too, and it's common for a type to store pointers to\nitself in situations such as a binary tree or a linked list. We can even define\n`json_value` as a recursive type using a union:\n\n```cpp\nclass json_value {\n    int state = -1;\n    union {\n        /* state 0 */ std::nullptr_t m_null;\n        /* state 1 */ bool m_bool;\n        /* state 2 */ double m_number;\n        /* state 3 */ std::string m_string;\n        /* state 4 */ std::map\u003cstd::string, json_value\u003e m_object;\n        /* state 5 */ std::vector\u003cjson_value\u003e m_array;\n    };\n\n   public:\n    json_value(std::nullptr_t) : state(0), m_null() {}\n    json_value(bool b) : state(1), m_bool(b) {}\n    json_value(double d) : state(2), m_number(d) {}\n    json_value(std::string const\u0026 str) : state(3), m_string(str) {}\n    json_value(std::map\u003cstd::string, json_value\u003e const\u0026 map) : state(4), m_object(map) {}\n    json_value(std::vector\u003cjson_value\u003e const\u0026 arr) : state(5), m_object(m_array) {}\n\n    // Now we need to write move constructors...\n    json_value(std::string\u0026\u0026 str) : state(3), m_string(std::move(str)) {}\n    json_value(std::map\u003cstd::string, json_value\u003e\u0026\u0026 map) : state(4), m_object(std::move(map)) {}\n    json_value(std::vector\u003cjson_value\u003e\u0026\u0026 arr) : state(5), m_object(std::move(m_array)) {}\n\n    // Now assignment operators...\n\n    // Now comparison operators...\n\n    // .emplace() might be nice\n\n    // Oh hey did we forget copy and move assignment for json_value?\n\n    // maybe we should have a way to check what the index of the currently active element is\n};\n```\n\n## `std::variant` is only a partial solution\n\nIt quickly becomes apparent that the class we're writing is essentially a\nspecialization of `std::variant`, which (thankfully) simplifies a lot of the\ncode:\n\n```cpp\nclass json_value {\n    std::variant\u003c\n        std::nullptr_t,\n        bool,\n        double,\n        std::string,\n        std::map\u003cstd::string, json_value\u003e,\n        std::vector\u003cjson_value\u003e\u003e value;\n\n   public:\n    json_value() = default;\n    json_value(json_value const\u0026) = default;\n    json_value(json_value\u0026\u0026) = default;\n    template \u003cclass T\u003e\n    json_value(T\u0026\u0026 object) : value(std::forward\u003cT\u003e(object)) {}\n\n\n    // Now assignment operators...\n\n    // Now comparison operators...\n\n    // .emplace() might be nice\n\n    // maybe we should have a way to check what the index of the currently active element is\n};\n```\n\nThis is better, and signifigantly less bug prone, but there's still a lot of\nboilerplate code that needs to be written.\n\nHere's the thing: at it's core, some types (like json_value) are best expressed\nas sum types! Anything we write to wrap one will just be a specialization of a\nsum type, and will usually involve a lot of boilerplate code to do the wrapping.\n\nAt this point, it might be prudent to ask: can we define `json_value` directly\nas a `std::variant`? Would a recursive `using` declaration work? I wish it did.\nI really, really wish it did.\n\n```cpp\n// LIES!!! This code doesn't work\n// error: use of undeclared identifier 'json_value'\nusing json_value = std::variant\u003c\n    std::nullptr_t,                          // json null\n    bool,                                    // json boolean\n    double,                                  // json number\n    std::string,                             // json string\n    std::map\u003cstd::string, json_value\u003e,       // json object\n    std::vector\u003cjson_value\u003e\u003e;                // json array\n```\n\n## The Recursive Variant Authority provides a complete solution!\n\n`rva::variant` allows you to write recursive variants _without any boilerplate_\nby passing `rva::self_t` in the places where you want your recursive type to go!\nWhen you write a statement like this, `rva::variant` will replace instances of\n`rva::self_t` with a properly specified instance of it's type, and it does this\nas a paper-thin wrapper over `std::variant` that provides all your boilerplate\ncode for you. And there was a _lot_ of boilerplate code to write.\n\n```cpp\nusing json_value = rva::variant\u003c\n    std::nullptr_t,                       // json null\n    bool,                                 // json boolean\n    double,                               // json number\n    std::string,                          // json string\n    std::map\u003cstd::string, rva::self_t\u003e,   // json object, type is std::map\u003cstd::string, json_value\u003e\n    std::vector\u003crva::self_t\u003e\u003e;            // json array, type is std::vector\u003cjson_value\u003e\n```\n\n`rva::variant` provides the full set of functionality given by `std::variant`,\nincluding:\n\n- `visit`,\n- `get`,\n- `get_if`,\n- `holds_alternative`,\n- `std::variant_size`,\n- `std::variant_alternative`,\n- `std::hash`)\n\nAnd it does so as a drop-in replacement (or as close to one as the standard\nwould allow)!\n\nThe Recursive Variant Authority hopes that you enjoy your time using\n`rva::variant`, and we encourage you to make full use of it's capabilities when\nwriting your code.\n\n## Usage \u0026 Installation\n\nThe most straight-forward way to use `rva::variant` is by using\n[CMake's FetchContent interface](https://cmake.org/cmake/help/v3.21/module/FetchContent.html)\nto find and fetch the library:\n\n```cmake\nFetchContent_Declare(\n    rva\n    GIT_REPOSITORY https://github.com/codeinred/recursive-variant.git\n    GIT_TAG        main\n)\nFetchContent_MakeAvailable(rva)\n```\n\nAlternatively, you can install it as a CMake package like so. _Please note that\nit's not necessary to build it in release mode, as it's a header-only library._\n\n```bash\ngit clone https://github.com/codeinred/recursive-variant.git rva\ncd rva\ncmake -B build -DBUILD_TESTING=OFF\ncmake --build build\nsudo cmake --install build\n```\n\nOnce installed, the library can then be discovered as a CMake package:\n\n```cmake\nfind_package(rva REQUIRED)\n```\n\nIn either case, whether obtained via `FetchContent`, or installed as a package,\nyou can use it via `target_link_libraries`. The library is header-only, but this\nwill ensure that it's added to the include path for that target.\n\n```cmake\ntarget_link_libraries(\u003cyour target\u003e PRIVATE rva::rva)\n```\n\n### Running tests\n\nYou may have noticed that the installation process \"built\" the library with\ntesting off. Installing the library doesn't require running tests, however if\nyou wish to run them, you may do so by ommitting the flag disabling testing, and\nthen running `build/test_rva -s`. Testing is done via the\n[Catch2 Testing framework](https://github.com/catchorg/Catch2). You will see a\nlot of `{?}` in the test output, but that's just because Catch2 doesn't know how\nto print a variant.\n\nTests may be found in the `test/` directory.\n\n```bash\ngit clone https://github.com/codeinred/recursive-variant.git rva\ncd rva\ncmake -B build\ncmake --build build -j 8\nbuild/test_rva -s\n```\n","funding_links":[],"categories":["Containers and Algorithms"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeinred%2Frecursive-variant","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodeinred%2Frecursive-variant","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeinred%2Frecursive-variant/lists"}