{"id":22248306,"url":"https://github.com/volumegraphics/traits","last_synced_at":"2026-02-25T19:10:10.848Z","repository":{"id":260050267,"uuid":"880132263","full_name":"VolumeGraphics/traits","owner":"VolumeGraphics","description":"Traits for C++","archived":false,"fork":false,"pushed_at":"2024-11-28T13:49:49.000Z","size":84,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-28T14:41:50.442Z","etag":null,"topics":["c-plus-plus","cpp20","cross-platform","header-only","library","modern","polymorphism","traits","type-erasure","value-semantics"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VolumeGraphics.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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-29T07:11:19.000Z","updated_at":"2024-11-28T13:49:53.000Z","dependencies_parsed_at":"2024-10-29T08:31:11.244Z","dependency_job_id":"c6b5fda5-f951-43d2-9439-d1c4b8891e9a","html_url":"https://github.com/VolumeGraphics/traits","commit_stats":null,"previous_names":["volumegraphics/traits"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Ftraits","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Ftraits/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Ftraits/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Ftraits/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VolumeGraphics","download_url":"https://codeload.github.com/VolumeGraphics/traits/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227863165,"owners_count":17831199,"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":["c-plus-plus","cpp20","cross-platform","header-only","library","modern","polymorphism","traits","type-erasure","value-semantics"],"created_at":"2024-12-03T06:14:58.928Z","updated_at":"2026-02-25T19:10:10.802Z","avatar_url":"https://github.com/VolumeGraphics.png","language":"C++","readme":"# Traits for C++\n\n[![windows](https://github.com/VolumeGraphics/traits/actions/workflows/windows.yml/badge.svg?cache-control=no-cache)](https://github.com/VolumeGraphics/traits/actions/workflows/windows.yml)\n[![linux](https://github.com/VolumeGraphics/traits/actions/workflows/linux.yml/badge.svg?cache-control=no-cache)](https://github.com/VolumeGraphics/traits/actions/workflows/linux.yml)\n[![macos](https://github.com/VolumeGraphics/traits/actions/workflows/macos.yml/badge.svg?cache-control=no-cache)](https://github.com/VolumeGraphics/traits/actions/workflows/macos.yml)\n[![lint](https://github.com/VolumeGraphics/traits/actions/workflows/lint.yml/badge.svg?cache-control=no-cache)](https://github.com/VolumeGraphics/traits/actions/workflows/lint.yml)\n[![codecov](https://codecov.io/github/VolumeGraphics/traits/graph/badge.svg?token=BK2TDD58X6)](https://codecov.io/github/VolumeGraphics/traits)\n\n[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)\n\nDefine shared behavior in a non-intrusive way while preserving value semantics.\n\nThis library is inspired by [Rust Traits](https://doc.rust-lang.org/book/ch10-02-traits.html) and previous projects like [Dyno](https://github.com/ldionne/dyno).\n\n\u003e [!CAUTION]\n\u003e At this point, this library is experimental and it is a pure curiosity.\n\u003e **No stability of interface or quality of implementation is guaranteed.**\n\u003e Some design decisions are likely to change\n\u003e and have a big effect on the way the library is used.\n\u003e Use at your own risks.\n\n## Quick Start\n\n*traits* is a single header C++20 library. To use the library, make sure you meet the [minimum requirements](#minimum-requirements) and just include the header file [traits.h](https://github.com/VolumeGraphics/traits/blob/main/include/traits.h) in your source code.\nAlternatively, you can first try it out in [Compiler Explorer](https://godbolt.org/z/Eh5nKr3jT).\n\nCMake projects might build, install and `find_package(traits)` or use fetch content:\n\n```cmake\ninclude(FetchContent)\nFetchContent_Declare(traits URL https://github.com/VolumeGraphics/traits/releases/latest/download/traits.zip)\nFetchContent_MakeAvailable(traits)\n```\n\nThere are currently no plans to support [vcpkg](https://learn.microsoft.com/en-us/vcpkg/get_started/overview) or [conan](https://conan.io/), as I do not recommend using the library in a productive environment ([see below](#motivation)).\n\n### Canonical usage example\n\nLet's assume that we have a set of types representing different shapes.\nWe can use a trait to model the behavior that all shapes have in common, such as calculating the area.\nThis behavior can then be defined individually for each type and does not need to be part of the original type definition:\n\n```c++\n#include \u003ciostream\u003e\n#include \u003cnumbers\u003e\n#include \u003cvector\u003e\n\n#include \"traits.h\"\nusing namespace traits;\n\nstruct Circle {\n    double radius{0.0};\n};\n\nstruct Square {\n    double length{0.0};\n};\n\nconstexpr auto Shape = trait{\n    TRAITS_METHOD(area, double() const),\n};\n\nconstexpr auto get(impl_for\u003cShape, Circle\u003e) {\n    return \"area\"_method = [](Circle const\u0026 circle) {\n        return std::numbers::pi * circle.radius * circle.radius;\n    };\n}\n\nconstexpr auto get(impl_for\u003cShape, Square\u003e) {\n    return \"area\"_method = [](Square const\u0026 square) {\n        return square.length * square.length;\n    };\n}\n\nint main() {\n    std::vector\u003csome\u003cShape\u003e\u003e someShapes;\n\n    someShapes.emplace_back(Circle{1.0});\n    someShapes.emplace_back(Square{1.0});\n\n    for (auto const\u0026 shape : someShapes)\n        std::cout \u003c\u003c \"Shape with area \" \u003c\u003c shape.area() \u003c\u003c \"\\n\";\n}\n```\n\n## \u003ca name=\"motivation\"\u003eMotivation\u003c/a\u003e\n\nPolymorphism is probably used too often. Nevertheless, it remains a useful concept for numerous use cases. Unfortunately, the standard approach to runtime polymorphism in C++ has some disadvantages, as many have already pointed out.\n\nFor example, polymorphism via inheritance breaks value semantics and typically forces you to use dynamic memory management. This not only harbors risks for unsafe code (e.g. in the case of ignorance of modern language concepts), but above all leads to unnecessary complexity.\n\nThis project was primarily intended as a personal learning experience:\n- I wanted to explore ways to avoid accidental complexity\n- I wanted to deepen my understanding of cross-language concepts and their implementation in other languages\n- I wanted to become more familiar with current C++ features and learn techniques that are useful for other tasks\n\nWhile I am very happy with the outcome, the resulting code is not yet ready for production and probably never will be.\n\n\u003e [!CAUTION]\n\u003e **You should *NOT* use this library in productive environments.**\n\nIn general, it is a bad idea to implement such a feature at the library level:\n- The implementation is very complex and therefore difficult to maintain\n  - Only works with the help of (a few) macros\n  - Various workarounds for compiler bugs and language restrictions\n  - Probably also some serious bugs in the implementation\n- Often really bad error messages (not only but also because of the macros)\n- Poor compilation times and some annoying compiler warnings\n- Bad debugging experience\n- No additional support from the IDE\n- Only some of these problems could be mitigated, e.g. through precompiled traits\n\nSuch library-level implementations ultimately highlight the current weaknesses of C++ and hopefully increase the incentives for the C++ committee to address these shortcomings in the language itself, as they emphasize the community's need for such features.\n\nThis implementation shows once again that polymorphism can be easily combined with value semantics. In many cases, this reduces the amount of code that deals with dynamic memory allocation and thus potentially unsafe code. Ultimately, developers can concentrate more on the what and not on the how.\n\n## Related projects\n\nThere have been some exciting projects dedicated to this topic for a long time:\n- [Dyno: Runtime polymorphism done right](https://github.com/ldionne/dyno)\n- [Boost.TypeErasure](https://www.boost.org/doc/libs/1_86_0/doc/html/boost_typeerasure.html)\n- [TE](https://github.com/boost-ext/te)\n- [Folly/Poly](https://github.com/facebook/folly/blob/main/folly/docs/Poly.md)\n- [EnTT/poly](https://github.com/skypjack/entt/blob/master/docs/md/poly.md)\n- [Cpp Dyn](https://github.com/qnope/CppDyn)\n- [Traits](https://github.com/Morglod/cpp_traits)\n- ...\n\nBut new implementations are also emerging more recently:\n- [AnyAny](https://github.com/kelbon/AnyAny)\n- [Proxy: Next Generation Polymorphism in C++](https://github.com/microsoft/proxy)\n- [Erased](https://github.com/qnope/erased)\n\nIdeas for offering this feature at the language level seem to make the most sense:\n- [Typeclasses in C++](https://github.com/TartanLlama/typeclasses)\n \nYou might also take also a look at the rust documentation to get familiar with the basic idea of [traits](https://doc.rust-lang.org/book/ch10-02-traits.html). Some explanations from there have been included in this documentation.\n\n## \u003ca name=\"minimum-requirements\"\u003eMinimum Requirements\u003c/a\u003e\n\nCMake version 3.24 or higher is required to build the library.\n\n### \u003ca name=\"compiler-requirements\"\u003e Compiler\n\n| Family | Minimum version | Required flags |\n| ------ | --------------- | -------------- |\n| clang  | 16.0.0          | -std=c++20     |\n| gcc    | 13.3            | -std=c++20     |\n| MSVC   | 19.34           | /std:c++20     |\n\n## Using the library ... step by step\n\nA trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way.\n\n\u003e [!TIP]\n\u003e All of the code below can be found in the [example](https://github.com/VolumeGraphics/traits/blob/main/example/readme.cpp).\n\u003e To keep this overview compact, definitions that have already been used in previous examples are not repeated. It is therefore highly recommended that you read all the examples in sequence.\n\n### traits allow you to define shared behavior with a declarative syntax\n\nA type’s behavior consists of the methods we can call on that type.\n\n```c++\nconstexpr auto WithAuthor = trait\n{\n    Method\u003c\"author\", std::string() const\u003e\n};\n```\n\nEvery method is uniquely identified by its signature, which consists of a name and a function type.\n\nA type that supports this trait must therefore offer a method with exactly this signature.\nWith the help of a special `target` type it is also possible to explicitly mention the target object in the signature, i.e.\n\n```c++\nconstexpr auto WithAuthor = trait\n{\n    Method\u003c\"author\", std::string(target const\u0026 self)\u003e\n};\n```\n\nis an equivalent definition of the `WithAuthor` trait.\nThis syntax is more explicit and is also more similar to the syntax for trait implementations, but is also more verbose.\n\n\u003e [!IMPORTANT]  \n\u003e `Method\u003c\u003e` refers to a predefined variable template.\n\u003e To be able to use this syntax, you must first make exactly the same method name available for the traits library with the help of a macro that is used in the global namespace.\n\u003e \n\u003e ```c++\n\u003e TRAITS_METHOD_DECLARATION(author);\n\u003e ```\n\nThere is an alternative syntax for defining traits without having to declare a method name first.\n\n```c++\nconstexpr auto WithSummary = trait\n{\n    TRAITS_METHOD (summary, std::string() const) // no previous declaration of 'summary' necessary\n};\n```\n\n\u003e [!TIP]\n\u003e Please always pay attention to the canonical spelling of method names. For example, no extra spaces should appear in overloaded operators.\n\n### traits can be used to constrain generic types (static polymorphism)\n\nDifferent types share the same behavior if we can call the same methods on all of those types.\n\nInstead of ...\n\n```c++\ndecltype (auto) operator\u003c\u003c (std::ostream\u0026 stream, auto const\u0026 drawable)\nrequires requires { { drawable.draw(stream) } -\u003e std::same_as\u003cvoid\u003e; }\n{\n    drawable.draw (stream);\n    return stream;\n}\n```\n\n... or ...\n\n```c++\ntemplate \u003ctypename T\u003e\nconcept Drawable = requires (T drawable, std::ostream\u0026 stream) { { drawable.draw(stream) } -\u003e std::same_as\u003cvoid\u003e; };\n\ndecltype (auto) operator\u003c\u003c (std::ostream\u0026 stream, Drawable auto const\u0026 drawable)\n{\n    drawable.draw (stream);\n    return stream;\n}\n```\n\n... you can use a trait like the one in the initial example above:\n\n```c++\ndecltype (auto) operator\u003c\u003c (std::ostream\u0026 stream, is\u003cDrawable\u003e auto const\u0026 drawable)\n{\n    drawable.draw (stream);\n    return stream;\n}\n\nauto drawCircle ()\n{\n    std::cout \u003c\u003c Circle{3.0};\n}\n```\n\n`is\u003c'trait'\u003e` is a C++ concept provided by the library that checks the type without `const` or `volatile` modifiers and as a non-reference type (i.e. the result of `std::remove_cvref_t`).\nThis makes it easier to use this concept for forwarding references.\n\n### traits can have multiple behaviors\n\nTrait definitions are a way to group method signatures together to define a set of behaviors necessary to accomplish some purpose.\n\n```c++\nconstexpr auto Runnable = trait\n{\n    Method\u003c\"start\", void()\u003e,\n    Method\u003c\"stop\", void()\u003e,\n\n    Method\u003c\"isRunning\", bool() const\u003e,\n};\n\nvoid run (is\u003cRunnable\u003e auto\u0026 runnable)\n{\n    if (not runnable.isRunning())\n    {\n        runnable.start ();\n\n        // ...\n\n        runnable.stop ();\n    }\n}\n```\n\n### traits support overloaded methods \n\n```c++\nconstexpr auto OverloadedConstness = trait\n{\n    Method\u003c\"bar\", void() const\u003e,\n    Method\u003c\"bar\", void()\u003e,\n};\n\nconstexpr auto OverloadedArgumentType = trait\n{\n    Method\u003c\"bar\", void(float value)\u003e,\n    Method\u003c\"bar\", void(double value)\u003e,\n};\n\nconstexpr auto OverloadedValueCategory = trait\n{\n    Method\u003c\"bar\", void(int const\u0026 lvalue)\u003e,\n    Method\u003c\"bar\", void(int\u0026 lvalue)\u003e,\n    Method\u003c\"bar\", void(int\u0026\u0026 rvalue)\u003e,\n};\n\nconstexpr auto OverloadedArity = trait\n{\n    Method\u003c\"bar\", void(bool value)\u003e,\n    Method\u003c\"bar\", void()\u003e,\n};\n```\n\n### traits support (certain) overloaded operators\n\n```c++\nconstexpr auto Callback = trait\n{\n    Method\u003c\"operator()\", void()\u003e,\n};\n\nvoid myAlgorithm (is\u003cCallback\u003e auto\u0026 eventProcessing)\n{\n    // ...\n\n    eventProcessing();\n\n    // ...\n\n    eventProcessing();\n\n    //...\n}\n```\n\n\u003e [!TIP]\n\u003e The function call operator does not have to be declared separately with `TRAITS_METHOD_DECLARATION(operator())`.\n\n### traits can be templated\n\n```c++\ntemplate \u003ctypename T\u003e\nconstexpr auto ValidatorFor = trait\n{\n    Method\u003c\"check\", bool(T const\u0026) const\u003e,\n};\n\nconstexpr auto IntValidator = ValidatorFor\u003cint\u003e;\n```\n\n### traits are composable\n\ntraits can be combined with `+` (this syntax is borrowed from Rust) ...\n\n```c++\nvoid print (std::ostream\u0026 out, is\u003cWithAuthor + WithSummary\u003e auto const\u0026 article)\n{\n    out \u003c\u003c std::format (\"{} by {}\\n\", article.summary(), article.author());\n}\n```\n\n... but they also support a boolean syntax:\n\n```c++\nconstexpr auto WithAuthorAndSummary = WithAuthor and WithSummary; // declare trait for later reuse\n```\n\n### traits support additional type constraints\n\nIn contrast to rust, these traits support an optional constraint at the beginning of the parameter list.\n\nA constraint is a templated callable: `\u003ctypename\u003e () -\u003e bool`\n\n```c++\nconstexpr auto DefaultConstructible = [] \u003ctypename T\u003e () { return std::is_default_constructible_v\u003cT\u003e; };\n```\n\nA number of use cases are supported by constraints.\n\n#### constraints can check arbitrary type properties\n\nSometimes you want to ensure not only the behaviors of a type, but also other characteristics.\n\n```c++\nconstexpr auto Empty = [] \u003ctypename T\u003e () { return std::is_empty_v\u003cT\u003e; };\n\nconstexpr auto StatelessAllocator = trait\n{\n    Empty and DefaultConstructible,\n\n    Method\u003c\"alloc\", void* (std::size_t byteCount) const\u003e,\n    Method\u003c\"free\" , void  (void* ptr) const\u003e\n};\n```\n\n#### constraints allow easy definition of derived constraints\n\nC++ concepts are not first class citizens at the moment:\n- you can't pass them as template parameters\n- it is complicated to define derived concepts\n\nLook at this example:\n\n```c++\nstruct Any final\n{\n    Any (auto\u0026\u0026 value); // OOPS ... clashes with copy/move constructor\n\n\n\n    // let's define a constructor which takes anything but ourselves instead\n\n    // 1. this syntax is currently not allowed\n    Any (not std::same_as\u003cAny\u003e auto\u0026\u0026 value);\n\n    // 2. this syntax is somewhat awkward\n    Any (auto\u0026\u0026 value) requires (not std::same_as\u003cstd::remove_cvref_t\u003cdecltype(value)\u003e, Any\u003e);\n\n    // 3. this syntax requires explicit definition of another concept, see below\n    Any (not_same_as\u003cAny\u003e auto\u0026\u0026 value);\n};\n\ntemplate \u003ctypename T, typename U\u003e\nconcept not_same_as = not std::same_as\u003cstd::remove_cvref_t\u003cT\u003e, U\u003e; // sic! T might be deduced to a reference type\n```\n\nOn the other hand, with a constraint ...\n\n```c++\ntemplate \u003ctypename U\u003e\nconstexpr auto SameAs = [] \u003ctypename T\u003e () { return std::same_as\u003cT, U\u003e; };\n```\n\n... we can define derived constraints as required, because they support all common boolean operators:\n\n```c++\nstruct Any\n{\n    Any (is\u003cnot SameAs\u003cAny\u003e\u003e auto\u0026\u0026 value);\n};\n```\n\n\u003e [!NOTE]  \n\u003e `is\u003c'constraint'\u003e` is equivalent to `is\u003ctrait{'constraint'}\u003e`\n\nThis check will work even when value will be deduced as reference type.\n\n#### constraints can be used to force strong(er) coupling\n\nIt may be advantageous to manage all implementations of a trait in a class hierarchy because, for example, the IDE supports inheritance particularly well.\n\n```c++\ntemplate \u003ctypename Interface\u003e\nconstexpr auto DerivedFrom = [] \u003ctypename T\u003e () { return std::derived_from\u003cT, Interface\u003e; };\n\nstruct TestableMarker\n{        \n};\n\nconstexpr auto Testable = trait\n{\n    DerivedFrom\u003cTestableMarker\u003e, // make it easier to find all testable elements in the code base\n\n    Method\u003c\"runTests\", bool() const\u003e,\n};\n```\n\n#### constraints allow easy definition of variant types\n\nGiven a simple constraint:\n\n```c++\ntemplate \u003ctypename... Types\u003e\nrequires (sizeof...(Types) \u003e 1)\nconstexpr auto OneOf = [] \u003ctypename T\u003e () { return (... or std::same_as\u003cT, Types\u003e); };\n```\n\nWe can easily define variant types.\n\n```c++\nvoid printArea (is\u003cOneOf\u003cCircle, Square\u003e\u003e auto shape)\n{\n    if constexpr (std::same_as\u003cdecltype (shape), Circle\u003e)\n        std::cout \u003c\u003c std::format (\"Circle area = {}\\n\", std::numbers::pi * shape.radius * shape.radius);\n    else\n        std::cout \u003c\u003c std::format (\"Square area = {}\\n\", shape.length * shape.length);\n}\n```\n\nAnd use them as expected.\n\n```c++\nprintArea (Circle{1.0});\nprintArea (Square{2.0});\n```\n\n\u003e [!NOTE]  \n\u003e There is a bug in the current MSVC compilers, so the constraint should actually be written as follows:\n\u003e ```c++\n\u003e // template \u003ctypename... Types\u003e\n\u003e // requires (sizeof...(Types) \u003e 1)\n\u003e // constexpr auto OneOf = [] \u003ctypename T\u003e () { return (... or std::same_as\u003cT, Types\u003e); };\n\u003e\n\u003e template \u003ctypename... Types\u003e\n\u003e requires (sizeof...(Types) \u003e 1)\n\u003e struct one_of\n\u003e {\n\u003e     template \u003ctypename T\u003e\n\u003e     constexpr auto operator() () const noexcept\n\u003e     {\n\u003e         return (... or std::same_as\u003cT, Types\u003e);\n\u003e     }\n\u003e };\n\u003e\n\u003e template \u003ctypename... Types\u003e\n\u003e requires (sizeof...(Types) \u003e 1)\n\u003e constexpr auto OneOf = one_of\u003cTypes...\u003e{};\n\u003e ```\n\n### traits support default method implementations\n\nSometimes it’s useful to have default behavior for some or all of the methods in a trait instead of requiring implementations for all methods on every type. \n\n```c++\nconstexpr auto Action = trait\n{\n    Method\u003c\"run\", bool()\u003e,\n\n    // many actions don't need initialization\n    Method\u003c\"init\", bool()\u003e = [] ([[maybe_unused]] auto\u0026 action)\n    {\n        return true;\n    },\n\n    // cleanup neither\n    Method\u003c\"cleanup\", void()\u003e = [] ([[maybe_unused]] auto\u0026 action)\n    {\n    }\n};\n```\n\nHowever, instead of ...\n\n```c++\nauto run (is\u003cAction\u003e auto\u0026 action)\n{\n    if (not action.init ()) // OOPS ... may not compile\n        return false;\n\n    const bool ok = action.run();\n\n    action.cleanup (); // OOPS ... may not compile\n    return ok;\n}\n```\n\n... you’ll then have to write:\n\n```c++\nauto run (is\u003cAction\u003e auto\u0026 action)\n{\n    auto action_impl = as\u003cAction\u003e (action); // OR: trait_cast\u003cAction\u003e (action)\n\n    if (not action_impl.init ())\n        return false;\n\n    const bool ok = action_impl.run();\n\n    action_impl.cleanup ();\n    return ok;\n}\n```\n\n`as\u003c'trait'\u003e (lvalue_ref)` creates a reference wrapper which provides all trait behaviors as public API.\n\n\u003e [!TIP]\n\u003e You should always access trait behaviors of an object via the reference wrapper (even when behaviors do not have a default implementation) because traits allow behaviors to be defined non-intrusively (see below).\n\nNow this code compiles and uses the given default implementations:\n\n```c++\nstruct SimpleAction\n{\n    bool run ()\n    {\n        return true;\n    }\n};\n\nauto runSimpleAction ()\n{\n    auto action = SimpleAction{};\n    return run (action);\n}\n```\n\n### traits allow you to implement behavior in a non-intrusive manner\n\nGiven some type for which we want to support all `Action` behaviors from above ...\n\n```c++\nstruct ForeignAction\n{\n    enum class Status { Failed, Ok };\n\n    auto execute ()\n    {\n        if (not ready)\n            return Status::Failed;\n\n        // ...\n\n        return Status::Ok;\n    }\n\n    bool ready{false};\n};\n```\n\n... we can provide an implementation of the `Action` trait in the same namespace (so ADL kicks in):\n\n```c++\nconstexpr auto get (impl_for\u003cAction, ForeignAction\u003e)\n{\n    return impl\n    {\n        \"run\"_method = [] (ForeignAction\u0026 action) -\u003e bool\n        {\n            return action.execute () == ForeignAction::Status::Ok;\n        },\n        \"init\"_method = [] (ForeignAction\u0026 action) -\u003e bool\n        {\n            action.ready = true;\n            return true;\n        },\n        \"cleanup\"_method = [] (ForeignAction\u0026 action) -\u003e void\n        {\n            action.ready = false;\n        }\n    };\n}\n```\n\n\u003e [!NOTE]  \n\u003e `\"...\"_method` is a user-defined string literal to make the code more readable.\n\u003e You can also use the `Method\u003c\"...\"\u003e =` syntax which is a bit more consistent with the trait definition syntax.\n\u003e However, make sure that you omit the parameter for the function type, as this is automatically derived.\n\n\u003e [!IMPORTANT]  \n\u003e You must provide an implementation for all behaviors which do not already have a default implementation, but you can override a default behavior of course.\n\nLet’s test it:\n\n```c++\nauto runForeignAction ()\n{\n    auto action = ForeignAction{};\n    return run (action);\n}\n```\n\nA trait implementation is valid for all derived types, unless there is a more specialized implementation.\n\n```c++\nstruct DerivedForeignAction : ForeignAction\n{\n};\n\nauto runDerivedForeignAction ()\n{\n    auto action = DerivedForeignAction{};\n    return run (action);\n}\n```\n\nLet’s give another example:\n\n```c++\nstruct Tweet\n{\n    std::string user;\n    std::string text;\n\n    static auto getUser (Tweet const\u0026 tweet) { return tweet.user; }\n    static auto getText (Tweet const\u0026 tweet) { return tweet.text; }\n};\n```\n\nYou can also use function pointers instead of lambdas.\n\n```c++\nconstexpr auto get (impl_for\u003cWithAuthor, Tweet\u003e)\n{\n    return impl { \"author\"_method = \u0026Tweet::getUser };\n}\n```\n\nA slightly more compact syntax is also valid, because `impl` is only an optional wrapper to make the code more explicit.\n\n```c++\nconstexpr auto get (impl_for\u003cWithSummary, Tweet\u003e)\n{\n    return \"summary\"_method = \u0026Tweet::getText;\n}\n```\n\n\u003e [!TIP]  \n\u003e The short syntax also works for multiple methods and lambda implementations.\n\nWe can now use the type in a function that requires both traits.\n\n```c++\nvoid post (is\u003cWithAuthorAndSummary\u003e auto const\u0026 message)\n{\n    auto withAuthorAndSummary = as\u003cWithAuthorAndSummary\u003e (message);\n    std::cout \u003c\u003c std::format (\"{}: {}\\n\", withAuthorAndSummary.author(), withAuthorAndSummary.summary());\n}\n\nauto postSomeTweet ()\n{\n    post (Tweet{\"@elonmusk\", \"X \u003e Twitter\"});\n}\n```\n\nSo far we've only talked about static polymorphism, but ...\n\n### traits work very well with runtime polymorphism \n\nIntroducing ... `some\u003c'trait'\u003e`\n\n`some\u003c\u003e` has value semantics like `std::any`, but offers a public API that is defined by the trait.\nYou can think of `some\u003c\u003e` as generalization of `std::any` with `std::any` ~ `some\u003ctrait{}\u003e`.\n`some\u003c\u003e` is implicit constructible from anything which implements the trait.\n\n```c++\nauto onlyCheck (some\u003cAction\u003e\u0026 action)\n{\n    if (not action.init ())\n        return false;\n\n    action.cleanup ();\n    return true;\n}\n\nauto onlyCheckForeignAction ()\n{\n    auto action = some\u003cAction\u003e {ForeignAction{}};\n    return check (action);\n}\n```\n\n\u003e [!NOTE]  \n\u003e Here we no longer use static polymorphism and provide a function template, but `some\u003c\u003e` erases the concrete type and we only define a single (exportable) function.\n\nAnother example.\n\n```c++\nstruct FirstCallback\n{\n    void operator () () {}\n};\n\nstruct SecondCallback\n{\n    void operator () () {}\n};\n\nauto invokeCallbacks ()\n{\n    std::vector\u003csome\u003cCallback\u003e\u003e someCallbacks;\n\n    someCallbacks.emplace_back (FirstCallback{});\n    someCallbacks.emplace_back (SecondCallback{});\n\n    for (auto\u0026 callback : someCallbacks)\n        callback ();\n}\n```\n\nLast example.\n\n```c++\nstruct Foo\n{\n    void bar () {}\n    void bar () const {}\n    void bar (bool) {}\n    void bar (int const\u0026) {}\n    void bar (int\u0026) {}\n    void bar (int\u0026\u0026) {}\n    void bar (float) {}\n    void bar (double) {}\n};\n\nauto fooBar ()\n{\n    some\u003cOverloadedConstness\u003e overloadedConstness = Foo{};\n\n    std::as_const (overloadedConstness).bar();\n    overloadedConstness.bar();\n\n    some\u003cOverloadedArgumentType\u003e overloadedArgumentType = Foo{};\n\n    overloadedArgumentType.bar(1.0f);\n    overloadedArgumentType.bar(1.0);\n\n    some\u003cOverloadedValueCategory\u003e overloadedValueCategory = Foo{};\n\n    int i = 0;\n\n    overloadedValueCategory.bar(std::as_const (i));\n    overloadedValueCategory.bar(i);\n    overloadedValueCategory.bar(std::move (i));\n\n    some\u003cOverloadedArity\u003e overloadedArity = Foo{};\n\n    overloadedArity.bar(true);\n    overloadedArity.bar();\n}\n```\n\n## Using the library ... advanced concepts\n\n### precise control of the memory requirements\n\n`some\u003c\u003e` offers the following customization options:\n- small buffer optimization\n- inlined methods\n\n### unerasing some types\n\nIf you ever need to unerase the type stored within a `some\u003c\u003e`, you can ask with `.type()` for the `std::type_info` and try a `some_cast\u003cType\u003e` which behaves exactly like a `std::any_cast\u003cType\u003e`.\n\n```c++\nauto changeShape (some\u003cOneOf\u003cCircle, Square\u003e\u003e shape)\n{\n    if (shape.type () == typeid (Circle))\n        shape = Square { some_cast\u003cCircle\u003e (shape).radius / std::numbers::inv_sqrtpi };\n    else\n        shape = Circle { some_cast\u003cSquare\u003e (shape).length * std::numbers::inv_sqrtpi };\n\n    return shape;\n}\n\nauto changeShapeTest()\n{\n    auto circle = Circle{1.0};\n    printArea (circle);\n\n    auto square = some_cast\u003cSquare\u003e (changeShape (circle));\n    printArea (square);\n\n    auto circleAgain = some_cast\u003cCircle\u003e (changeShape (square));\n    printArea (circleAgain);\n}\n```\n\n### explicit support for variant types\n\nFor a number of reasons, it makes sense to explicitly support `some\u003c\u003e` variant types and offer an alternative to `std::variant`:\n- if you want to centrally define not only the possible types, but also the possible behaviors on these types\n- if you want to implement the variant behaviors separately for each type\n- if you require a different storage model for your variant type\n\n`some_variant\u003c'Types'...\u003e` is a type alias for a specially constrained `some\u003c\u003e` that can be used as a replacement for `std::variant`.\n\n`some\u003c\u003e` provides a `visit()` overload for this purpose:\n\n```c++\nvoid printCircumference (some_variant\u003cCircle, Square\u003e const\u0026 shape)\n{\n    visit (overload // famous overload pattern\n    {\n        [] (Circle const\u0026 circle)\n        {\n            std::cout \u003c\u003c std::format (\"Circle circumference = {}\\n\", std::numbers::pi * 2.0 * circle.radius);\n        },\n        [] (Square const\u0026 square)\n        {\n            std::cout \u003c\u003c std::format (\"Square circumference = {}\\n\", 4.0 * square.length);\n        }\n    }, shape);\n}\n\nauto printCircumferenceOfShapes ()\n{\n    printCircumference (Circle{1.0});\n    printCircumference (Square{2.0});\n}\n```\n\n`some_variant\u003c\u003e` provides no dedicated API other than `visit()`.\nThe size of a `some_variant\u003c\u003e` is large enough to store all alternatives inplace.\n\nHowever, you can also define `some_variant\u003c\u003e`s with additional constraints, expected behaviors or customized storage.\n`some\u003c\u003e` offers a special type alias template `variant` for this purpose:\n\n```c++\nconstexpr auto WithType = trait\n{\n    Method\u003c\"type\", std::string () const\u003e\n};\n\nusing Shape = some\u003cWithType\u003e::variant\u003cCircle, Square\u003e;\n\nconstexpr auto get (impl_for\u003cWithType, Circle\u003e)\n{\n    return \"type\"_method = [] (Circle const\u0026) -\u003e std::string { return \"Circle\"; };\n}\n\nconstexpr auto get (impl_for\u003cWithType, Square\u003e)\n{\n    return \"type\"_method = [] (Square const\u0026) -\u003e std::string { return \"Square\"; };\n}\n\nvoid printType (Shape const\u0026 shape)\n{\n    std::cout \u003c\u003c std::format (\"Type = {}\\n\", shape.type ());\n}\n\nauto printName ()\n{\n    printType (Circle{1.0});\n    printType (Square{2.0});\n}\n```\n\n## Tips for use\n\nSince traits are essentially used within `is\u003c...\u003e`, the trait names should be chosen appropriately to maintain a natural reading flow.\nFor this reason, a noun or the paraphrase *with ... behavior* instead of *has ... behavior* is used in all examples .\n\n## Implementation notes\n\nThe implementation uses snake case for all concepts, types and type aliases. CamelCase is used for all global variables.\n\nThe current implementation defines the following C++ concepts:\n- `function_type`: a function signature\n- `callable`: a valid `std::function` target\n- `method_id`: a unique identifier for a method\n- `constraint`: a test for any type attributes\n- `behavior`: a certain behavior\n- `behavior_implementation`: an implementation of a behavior\n- `is`: a type supports a specific trait\n\nThe following types are used in the implementation:\n- `method_name`: unique name of a method\n- `method_signature\u003cmethod_name, function_type\u003e` is the only implementation of the `method_id` concept\n- `method_implementation\u003cmethod_id, callable\u003e` is the only implementation of the `behavior_implementation` concept\n\n## Open issues\n\nHere is a list of possible API improvements, in no particular order:\n- traits: you must define an empty implementation of a trait, even if all methods have default implementations\n- constraints: add support for all boolean operators\n- behaviors: add support for more overloaded operators, esp. `operator\u003c\u003c`\n- function types: add support for noexcept\n- function types: add support for volatile\n- `some\u003c\u003e`: always has a value; use `optional\u003csome\u003c\u003e\u003e` instead or introduce `maybe_some\u003c\u003e`\n- `some\u003c\u003e`: add conversion from `some\u003c\u003e` other type\n- `some\u003c\u003e`: improve syntax for inlined methods\n\nHere is a list of possible implementation improvements, in no particular order:\n- fix internal linkage warning\n- remove dependency to std::tuple\n- remove dependency to std::variant\n- do not use unnamed inline namespaces\n- move method_kernel into method_name ?\n- hide non-public stuff in a detail namespace\n- better check for canonical method names\n- tests: check macro syntax with method inlining and trait implementations\n- document ADRs\n \n## Known limitations\n\nHere is a list of known problems:\n- *clang* generates a warning for unused traits, so they must be annotated with `[[maybe_unused]]` or the warnings must be suppressed in some other way\n \n## License\n\n*traits* is BSD-3 licensed, as found in the [LICENSE][l] file.\n\n[l]: https://github.com/VolumeGraphics/traits/blob/main/LICENSE\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolumegraphics%2Ftraits","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvolumegraphics%2Ftraits","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolumegraphics%2Ftraits/lists"}