{"id":20974742,"url":"https://github.com/arbrk1/typeclasses_cpp","last_synced_at":"2026-03-06T04:37:00.508Z","repository":{"id":214807366,"uuid":"157772080","full_name":"arbrk1/typeclasses_cpp","owner":"arbrk1","description":"Feature-complete typeclasses for C++","archived":false,"fork":false,"pushed_at":"2020-07-05T15:41:23.000Z","size":58,"stargazers_count":11,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T18:50:25.801Z","etag":null,"topics":["cpp","cpp-templates","crtp","traits","typeclasses"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arbrk1.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":"2018-11-15T21:00:38.000Z","updated_at":"2024-05-29T18:27:22.000Z","dependencies_parsed_at":"2023-12-30T23:30:21.255Z","dependency_job_id":"8742a02a-52fc-419c-aaf6-f8f8faca3cfe","html_url":"https://github.com/arbrk1/typeclasses_cpp","commit_stats":null,"previous_names":["arbrk1/typeclasses_cpp"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arbrk1%2Ftypeclasses_cpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arbrk1%2Ftypeclasses_cpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arbrk1%2Ftypeclasses_cpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arbrk1%2Ftypeclasses_cpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arbrk1","download_url":"https://codeload.github.com/arbrk1/typeclasses_cpp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254150547,"owners_count":22022976,"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","cpp-templates","crtp","traits","typeclasses"],"created_at":"2024-11-19T04:32:48.704Z","updated_at":"2026-03-06T04:36:55.480Z","avatar_url":"https://github.com/arbrk1.png","language":"C++","readme":"# Better typeclasses/traits for C++\n\nThis repository is an experiment with defining a rather complete \n[typeclass system](https://en.wikipedia.org/wiki/Type_class) inside \nthe C++ language. \n\n## Update (2020/07/05)\n\nThe nearing of the C++20 standard has led me to revise this repository.\n\nFollowing changes have been made:\n\n* a few erroneus statements on SFINAE fixed\n* a concept `Instance` introduced (see [tc_concept.hpp](./tc_concept.hpp))\n* some new examples devised\n\n## Introduction\n\nTypical uses of typeclasses are following:\n\n1. a solution to the \u0026#8220;expression problem\u0026#8221; (concerned with extending\n   a datatype both with new operations and new variants without touching \n   existing code)\n2. a rich interface (an interface with a small set of required methods and \n   a great supply of default methods defined in terms of the required ones \n   or in terms of each another)\n3. a compiler-checked logical constraint on a type or a tuple of types\n4. a dynamic-dispatch mechanism (e.g. existential types in Haskell or \n   dynamic traits in Rust)\n5. function overloading (using type inference to determine a \n   corresponding typeclass instance)\n\nTypeclasses in the mainstream languages are rare (despite their general \nusefulness): the only two languages with builtin support for typeclasses \nseem to be Haskell and Rust (and some aspects of typeclasses can be \nemulated in several other languages).\n\nIt appears that typeclasses \n(at least with respect to _all_ the usecases above) \ncan be easily described by C++ templates. Futhermore a typeclass system\nsupporting all the points above\ncan be implemented using only Standard C++98. Our implementation consists \nof the [tc.hpp](./tc.hpp) header-only library and some usage examples in the \n[samples](./samples/) directory.\n\nThe full C++98 implementation is very short: it consists \nof a `struct` declaration and three one-line variadic macros. \n\nThe C++11 implementation has in addition \na single type alias (which can be used in place of one of the previously \nmentioned C++98 macros), a single short struct definition and a single \none-line macro for forcing compile-time constraints.\n\n## Existing implementations (of which existence I am aware)\n\nThis is a (very likely incomplete) list of C++ typeclass implementations \nwhich can be easily googled.\n\n### \u0026#8220;Naive\u0026#8221; translation\n\nE.g. \u003chttp://www.nauths.fr/en/2015/04/26/cpp-typeclasses.html\u003e or \n\u003chttp://blog.shaynefletcher.org/2016/10/haskell-type-classes-in-ocaml-and-c.html\u003e.\n\nA typeclass is modelled by a simple templated `struct`:\n\n``` c++\ntemplate\u003cclass T\u003e\nstruct Monoid {\n    static T empty();\n    static T append(T, T);\n};\n```\n\nA typeclass instance is defined by the template specialization:\n\n``` c++\ntemplate\u003c\u003e\nstruct Monoid\u003cint\u003e {\n    static int empty() { return 0; }\n    static int append(int x, int y) { return x+y; }\n};\n```\n\nThe main drawback is that no part of the definition of \n`template\u003cclass T\u003e struct Monoid` is present in any of \nits specializations. So all the code in that definition \ndescribes only how the types for which the typeclass \nis not implemented behave. \n\nThe problem is that we have no _simple_  way of defining default method \nimplementations or enforcing logical constraints like \u0026#8220;all instances \nof the typeclass A must also be instances of the typeclass B\u0026#8221;.\n\n\nBecause the general typeclass template definition is concerned \nwith the case of \u0026#8220;type T is not an instance of the typeclass\u0026#8221;,\na much better version of the typeclass definition would be simply\n\n``` c++\ntemplate\u003cclass T\u003e struct Monoid;\n```\n\nThis line of thought gets us to [the following](#comparison-with-the-naive-version).\n\n\n### \u0026#8220;Less naive\u0026#8221; translation\n\nA blogpost \u003chttps://functionalcpp.wordpress.com/2013/08/16/type-classes/\u003e has \na little more elaborate definition using an explicit static boolean \nto distinguish between the types which implement the typeclass and the \ntypes which do not.\n\nThe problem with the inability to define default method implementations \nstill persists.\n\n\n### C++ concepts\n\nSometimes it is said that C++ concepts have lots in common with \ntypeclasses. Unfortunately it's only partially true: of the \nfive usecases listed in [the introduction](#introduction) the C++ concepts \nprovide only the third one: they group types into semantic categories \nso that a semantic category membership can be compile-time checked.\n\nSome of the other typeclass features were present in a previous \niteration of the concept system (so called \u0026#8220;C++0x concepts\u0026#8221;) but \ncurrently we have only a sort of \u0026#8220;Concepts Lite\u0026#8221;.\n\nIt may be interesting to compare concepts and typeclasses w.r.t. \ntheir logical strength.\n\nTypeclasses support recursion, but can use only a conjunctive part of logic \n(i.e. one can say \u0026#8220;type T belongs to a class X \nif T belongs to Y and to Z\u0026#8221;, but cannot say \u0026#8220;type T belongs \nto a class X if T belongs to at least one of Y and Z\u0026#8221;).\n\nConcepts __do not__ support recursion, but can use the logical \ndisjunction (with concepts there is no such a thing as \na conflict of implementations).\n\nAlso concepts are an \u0026#8220;if-and-only-if\u0026#8221; condition as opposed \nto typeclasses, which allow to express separate implications \nin both directions.\n\nNevertheless, the introduction of concepts allows one to replace \n[a rather clunky `TC_REQUIRE` macro](#constraining-implementations)\nwith a simple constraint `Instance` (see [tc_concept.hpp](./tc_concept.hpp)). \n\nThis constraint allows, for example, dispatching on the fact of \nsome type __not__ being an instance of some typeclass \n(see [concept.cpp](./samples/concept.cpp) and \n[show_concept.cpp](./samples/show_concept.cpp)). \nFor a workaround not using concepts \nsee [show_unshowable.cpp](./samples/show_unshowable.cpp).\n\n\n## This implementation\n\nOur implementation solves the issue that the template specializations \noverride the original definition by never specializing the definition \nof a typeclass. Instead all typeclass instances are stored as \nspecializations of a separate type (this type is same for all \ninstances of all typeclasses). This allows us to define the default \nmethods of a typeclass with a CRTP-like pattern.\n\n\n\nBelow we describe the structure and the workings of the `tc.hpp` header.\n\n\n### Defining typeclasses\n\nTo define a typeclass one uses a templated `struct` with some static \nmethods:\n\n``` c++\ntemplate\u003cclass T\u003e\nstruct Foo {\n    static void foo() = delete;\n};\n```\n\nDefault implementations (even mutually dependent) can be used:\n\n``` c++\ntemplate\u003cclass T\u003e\nstruct Foo {\n    // default typeclass methods are a sort of CRTP\n\n    static int foo(int x) {\n        TC_IMPL(Foo\u003cT\u003e) FooT; // this syntax is described below\n        return FooT::bar(x) + 1;\n    }\n\n    static int bar(int x) {\n        TC_IMPL(Foo\u003cT\u003e) FooT; \n        return FooT::foo(x) - 1;\n    }\n};\n```\n\nFor more info on default methods see [default.cpp](./samples/default.cpp).\n\n\n### Extending typeclasses with types\n\nAll typeclass instances are tracked by a single type `_tc_impl_`. \nIt's defined as\n\n``` c++\ntemplate\u003cclass T\u003e struct _tc_impl_;\n```\n\nAdding a type `Bar` to the typeclass `Foo` is as simple as\n\n``` c++\ntemplate\u003c\u003e\nstruct _tc_impl_\u003c Foo\u003cBar\u003e \u003e {\n    typedef struct: Foo\u003cBar\u003e { /* method implementations */ } type;\n};\n```\n\nTo facilitate such definitions a macro is introduced:\n\n``` c++\n#define TC_INSTANCE(tc, body...) \\\n    struct _tc_impl_\u003c tc \u003e { typedef struct: tc body type; };\n```\n\nIf the `tc` parameter has commas (e.g. `Show\u003c pair\u003cint,int\u003e \u003e` or \n`Foo\u003cBar, Baz\u003e`) a small helper variadic macro can be used to wrap \nthe parameter:\n\n``` c++\n#define TC(x...) x\n```\n\nIt should be noted that even in the case variadic macros are not supported \na helpful set of macros can be defined:\n\n``` c++\n#define TC_INSTANCE_BEGIN(tc) \\\n    struct _tc_impl_\u003c tc \u003e { typedef struct: tc\n#define TC_INSTANCE_END type; };\n#define COMMA ,\n```\n\nAnd now instead of\n\n``` c++\ntemplate\u003c\u003e\nTC_INSTANCE(TC(Foo\u003cBar,Baz\u003e), { \n    static void foo() { /* definition */ } \n})\n```\n\nwe must write\n\n``` c++\ntemplate\u003c\u003e\nTC_INSTANCE_BEGIN(Foo\u003cBar COMMA Baz\u003e) {\n    static void foo() { /* definition */ } \n} TC_INSTANCE_END\n```\n\n### Using typeclasses\n\nThe `tc.hpp` provides two ways of calling methods of a typeclass. \n\nThe first way is to use the `TC_IMPL` macro defined as\n\n``` c++\n#define TC_IMPL(tc...) typedef typename _tc_impl_\u003c tc \u003e::type\n```\n\nFor example:\n\n``` c++\nTC_IMPL(Foo\u003cBar,Baz\u003e) FBB;\n\nFBB::foo();\n```\n\nThe second (less verbose) way is to use a C++11-only templated \ntype alias\n\n``` c++\ntemplate\u003cclass T\u003e using tc_impl_t = typename _tc_impl_\u003cT\u003e::type;\n```\n\nIt is used as follows:\n\n``` c++\ntc_impl_t\u003cFoo\u003cBar,Baz\u003e\u003e::foo();\n```\n\nIt can be used in conjunction with `decltype` to get a sort of \npoor man's type inference.\n\nFortunately, typeclasses play rather well with template overloads (see \n[show.cpp](./samples/show.cpp) where a templated overload of `operator\u003c\u003c` \nis used to infer a correct typeclass instance) so in some cases \none doesn't need to specify any types at all.\n\n\n### Constraining implementations\n\nThe mechanism of C++ template instantiation provides an automatic \nchecking of typeclass instance \u0026#8220;real\u0026#8221; dependencies (required by \ntypeclass method implementations). E.g. with the following definition\n\n``` c++\ntemplate\u003cclass T\u003e\nTC_INSTANCE(Foo\u003cBar\u003cT\u003e\u003e, {\n    void foo() {\n        tc_impl_t\u003cFoo\u003cT\u003e\u003e::foo();\n    }\n})\n```\n\nan attempt to use `tc_impl_t\u003cFoo\u003cBar\u003cBaz\u003e\u003e\u003e::foo()` will fail \nif `Baz` doesn't implement the `Foo` typeclass.\n\nBut sometimes typeclasses are used as a sort of marker (e.g. Rust \nmarker traits like `Clone`): a fact that some relation holds \nbetween some types. Frequently such typeclasses have no methods so \nthe mechanism described above is useless.\n\nWe use a rather dumb solution: a `static_assert` checking if \na static `type` field is present on the specific `_tc_impl_` \nspecialization.\n\n``` c++\ntemplate\u003cclass T\u003e struct _tc_dummy_ { static bool const value = true; T t; };\n#define TC_REQUIRE(tc...) \\\n    static_assert( _tc_dummy_\u003ctc_impl_t\u003ctc\u003e\u003e::value, \"unreachable\" );\n```\n\nIt's very likely that a more beautiful solution exists.\n\n#### Update (2020/07/05)\n\nNow there definitely exists a more beautiful solution: beginning from C++20, \na requirement can be placed between `template\u003c...\u003e` and the corresponding \nclass or instance definition:\n\n``` c++\ntemplate\u003cclass T\u003e requires Instance\u003cFoo\u003cT\u003e\u003e // constraint on a class\nstruct Bar { ... };\n// Rust:    trait Bar: Foo { ... }\n// Haskell: class (Foo x) =\u003e Bar x where { ... }\n\ntemplate\u003cclass T\u003e requires Instance\u003cGood\u003cT\u003e\u003e // constraint on an instance\nTC_INSTANCE(Good\u003cFoo\u003cT\u003e\u003e, { ... });\n// Rust:    impl\u003cT: Good\u003e Good for Foo\u003cT\u003e { ... }\n// Haskell: instance (Good x) =\u003e Good (Foo x)\n\ntemplate\u003cclass T\u003e requires Instance\u003cFoo\u003cT\u003e\u003e // constraint on an overload\nvoid foo(T t) { ... }\n// Rust:    fn foo\u003cT: Foo\u003e(t: T) { ... }\n// Haskell: foo :: (Foo x) =\u003e x -\u003e IO ()\n//\n// Note: this analogy is not precise. C++ allows to define the second \n// \"fallback\" overload, which is selected for types NOT implementing Foo:\ntemplate\u003cclass T\u003e void foo(T t) { ... }\n```\n\n#### End of update\n\nAlso note that such a constraint can be placed using C++98-only constructs:\n\n``` c++\n// a constrained typeclass instance\nTC_INSTANCE(Foo\u003cBar\u003cT\u003e\u003e, {\n    TC_IMPL(Foo\u003cT\u003e) FooBar; \n    // instead of TC_REQUIRE(Foo\u003cT\u003e)\n\n    /* some methods */\n})\n\n// a constrained function\nvoid foo() {\n    TC_IMPL(Foo\u003cBaz\u003e) FooBaz;\n    FooBaz();\n    // two lines instead of a single one: TC_REQUIRE(Foo\u003cBaz\u003e)\n\n    /* some code */\n}\n```\n\nFor an example see the [constrained.cpp](./samples/constrained.cpp) file.\n\n\n### Existential types (aka typeclass-based dynamic dispatch)\n\nExistential types are a way of describing dynamic dispatch in terms of \na typeclass system. Simply stated, if we have a (one-parametric, \nfor the sake of simplicity) typeclass `C` then we have a \ntype `(exists a. C a =\u003e a)` which is a supertype of any instance of \nthe typeclass `C`. We'll use a shorter (Rust-like) notation `dyn C` below.\n\nTo describe such a type we must define two utility types. \nSuppose we have a typeclass\n\n``` c++\ntemplate\u003cclass T\u003e \nstruct Foo {\n    // we can dispatch only through a reference or a pointer\n    static void foo(T const \u0026x, int y) = delete;\n};\n```\n\nNow we define an abstract type corresponding to an existential \n`dyn Foo`\n\n``` c++\nstruct DynFoo {\n    // now the first argument becomes \"this\"\n    virtual void foo_self(int y) const = 0;\n    virtual ~DynFoo() {}\n};\n\ntemplate\u003c\u003e\nTC_INSTANCE(Foo\u003cDynFoo\u003e, {\n    static void foo(DynFoo const \u0026x, int y) {\n        x.foo_self(y);\n    }\n})\n```\nand a generic type to wrap objects of different types\n\n``` c++\ntemplate\u003cclass T\u003e\nstruct DynFooWrapper: DynFoo {\n    T self;\n\n    void foo_self(int y) const {\n        tc_impl_t\u003cFoo\u003cT\u003e\u003e::foo(self, y);\n    }\n\n    DynFooWrapper(T x): self(x) {}\n};\n```\n\nThe final step is to transform `DynFooWrapper\u003cT\u003e` values into an \ninstances of `DynFoo`. This can be done by the means of boxing:\n\n``` c++\ntemplate\u003cT\u003e\nstd::unique_ptr\u003cDynFoo\u003e to_foo(T x) {\n    return std::make_unique\u003cDynFooWrapper\u003cT\u003e\u003e(x);\n}\n```\n\nFinally, it's convenient to define a `Foo` instance for boxed values:\n\n``` c++\ntemplate\u003cT\u003e\nTC_INSTANCE(Foo\u003cstd::unique_ptr\u003cT\u003e\u003e, {\n    static void foo(std::unique_ptr\u003cT\u003e const \u0026x, int y) {\n        tc_impl_t\u003cFoo\u003cT\u003e\u003e::foo(*x, y);\n    }\n})\n```\n\nThis last step is the only step which must be explicitly done in Rust \n(and all these steps are done implicitly in Haskell). So consider \nthis only as a proof-of-concept demonstration that it is possible \n(albeit a little cumbersome) to adapt C++ inheritance-based dispatching \nto a typeclass system.\n\nAn example of existential types can be seen in \nthe [show.cpp](./samples/show.cpp) file.\n\n\n## Comparison with the \u0026#8220;naive\u0026#8221; version\n\nAt the cost of slightly complicating the macros the \u0026#8220;naive\u0026#8221; version \ndescribed [above](#naive-translation) could be endowed with the \nsame features as the [tc.hpp](./tc.hpp) implementation.\n\nSee the [tc_alt.hpp](./tc_alt.hpp) header and the \n[alt_samples](./alt_samples/) directory. Significant differences in \nthe usage are marked by a `// DIFFERENCE` comment.\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farbrk1%2Ftypeclasses_cpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farbrk1%2Ftypeclasses_cpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farbrk1%2Ftypeclasses_cpp/lists"}