{"id":15571006,"url":"https://github.com/netromdk/sigs","last_synced_at":"2025-10-03T00:31:45.195Z","repository":{"id":75034299,"uuid":"43163599","full_name":"netromdk/sigs","owner":"netromdk","description":"Simple thread-safe signal/slot C++17 include-only library.","archived":false,"fork":false,"pushed_at":"2023-06-17T15:20:17.000Z","size":529,"stargazers_count":38,"open_issues_count":0,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-01-13T09:43:53.879Z","etag":null,"topics":["connected-slots","cpp","cpp17","include-only","lambda","signal","signal-interface","slot","template-meta-programming"],"latest_commit_sha":null,"homepage":"","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/netromdk.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":"2015-09-25T17:17:09.000Z","updated_at":"2024-11-16T09:37:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"4397eee7-6747-440a-acb4-185c6af05281","html_url":"https://github.com/netromdk/sigs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netromdk%2Fsigs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netromdk%2Fsigs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netromdk%2Fsigs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/netromdk%2Fsigs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/netromdk","download_url":"https://codeload.github.com/netromdk/sigs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235054718,"owners_count":18928624,"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":["connected-slots","cpp","cpp17","include-only","lambda","signal","signal-interface","slot","template-meta-programming"],"created_at":"2024-10-02T17:53:04.089Z","updated_at":"2025-10-03T00:31:39.820Z","avatar_url":"https://github.com/netromdk.png","language":"C++","readme":"[![Test](https://github.com/netromdk/sigs/workflows/Test/badge.svg?branch=master)](https://github.com/netromdk/sigs/actions)\n[![Clang Sanitizers](https://github.com/netromdk/sigs/workflows/Clang%20Sanitizers/badge.svg?branch=master)](https://github.com/netromdk/sigs/actions)\n[![CodeQL](https://github.com/netromdk/sigs/workflows/CodeQL/badge.svg?branch=master)](https://github.com/netromdk/sigs/security/code-scanning)\n\n# sigs\nSimple thread-safe signal/slot C++17 library, which is templated and include-only. No linking required. Just include the header file \"*sigs.h*\".\n\nIn all its simplicity, the class `sigs::Signal` implements a signal that can be triggered when some event occurs. To receive the signal slots can be connected to it. A slot can be any callable type: lambda, functor, function, or member function. Slots can be disconnected when not needed anymore.\n\nA signal is triggered by invoking its `operator()()` with an optional amount of arguments to be forwarded to each of the connected slots' invocations. But they must conform with the parameter types of `sigs::Signal::SlotType`, which reflects the first template argument given when instantiating a `sigs::Signal`.\n\nTable of contents\n=================\n\n* [Examples](#examples)\n* [Ambiguous types](#ambiguous-types)\n* [Return values](#return-values)\n* [Signal interface](#signal-interface)\n* [Blocking signals and slots](#blocking-signals-and-slots)\n* [Customizing lock and mutex types](#customizing-lock-and-mutex-types)\n\nExamples\n========\nThe most simple use case is having a `void()` invoked:\n\n```c++\nsigs::Signal\u003cvoid()\u003e s;\ns.connect([]{ std::cout \u003c\u003c \"Hello, signals. I'm an invoked slot.\\n\"; });\ns(); // Trigger it, which will call the function.\n```\n\nAs mentioned above you can pass arbitrary arguments to the slots but the types will be enforced at compile-time.\n\n```c++\nsigs::Signal\u003cvoid(int, const std::string\u0026)\u003e s;\ns.connect([](int n, const std::string \u0026str) {\n  std::cout \u003c\u003c \"I received \" \u003c\u003c n \u003c\u003c \" and \" \u003c\u003c str \u003c\u003c std::endl;\n});\n\n// Prints \"I received 42 and I like lambdas!\".\ns(42, \"I like lambdas!\");\n\n// Error! \"no known conversion from 'char const[5]' to 'int' for 1st argument\".\ns(\"hmm?\", \"I like lambdas!\");\n```\n\nWhen connecting a slot the result is a `sigs::Connection`, and the connection can be disconnected by calling `sigs::Connection::disconnect()` or `sigs::Signal::disconnect(sigs::Connection)`.\n\n```c++\nsigs::Signal\u003cvoid()\u003e s;\ns.connect([]{ std::cout \u003c\u003c \"Hi\"; });\nauto conn = s.connect([]{ std::cout \u003c\u003c \" there!\\n\"; });\n\n// Prints \"Hi there!\".\ns();\n\n// Disconnect second slot.\nconn-\u003edisconnect();\n\n// Or by using the signal: s.disconnect(conn);\n\n// Prints \"Hi\".\ns();\n```\n\nNote that all slots can be disconnected by giving no arguments to `sigs::Signal::disconnect()`, or by calling `sigs::Signal::clear()`.\n\nSlots can be any callable type: lambda, functor, or function. Even member functions.\n\n```c++\nvoid func() {\n  std::cout \u003c\u003c \"Called function\\n\";\n}\n\nclass Functor {\npublic:\n  void operator()() {\n    std::cout \u003c\u003c \"Called functor\\n\";\n  }\n};\n\nclass Foo {\npublic:\n  void test() {\n    std::cout \u003c\u003c \"Called member fuction\\n\";\n  }\n};\n\nsigs::Signal\u003cvoid()\u003e s;\ns.connect(func);\ns.connect([]{ std::cout \u003c\u003c \"Called lambda\\n\"; });\ns.connect(Functor());\n\nFoo foo;\ns.connect(\u0026foo, \u0026Foo::test);\n\ns();\n\n/* Prints:\nCalled function\nCalled lambda\nCalled functor\nCalled member funtion\n*/\n```\n\nAnother useful feature is the ability to connect signals to signals. If a first signal is connected to a second signal, and the second signal is triggered, then all of the slots of the first signal are triggered as well - and with the same arguments.\n\n```c++\nsigs::Signal\u003cvoid()\u003e s1;\ns1.connect([]{ std::cout \u003c\u003c \"Hello 1 from s1\\n\"; });\ns1.connect([]{ std::cout \u003c\u003c \"Hello 2 from s1\\n\"; });\n\ndecltype(s1) s2;\ns2.connect(s1);\n\ns2();\n\n/* Prints:\nHello 1 from s1\nHello 2 from s1\n*/\n```\n\nA signal can be disconnected by using `sigs::Signal::disconnect(sigs::Signal\u0026)`, or the regular `sigs::Connection::disconnect()`.\n\nAmbiguous types\n===============\nSometimes there are several overloads for a given function and then it's not enough to just specify `\u0026Class::functionName` because the compiler does not know which overload to choose.\n\nConsider the following code:\n\n```c++\nclass Ambiguous {\npublic:\n  void foo(int i, int j) { std::cout \u003c\u003c \"Ambiguous::foo(int, int)\\n\"; }\n\n  void foo(int i, float j) { std::cout \u003c\u003c \"Ambiguous::foo(int, float)\\n\"; }\n};\n\nsigs::Signal\u003cvoid(int, int)\u003e s;\n\nAmbiguous amb;\ns.connect(\u0026amb, \u0026Ambiguous::foo); // \u003c-- Will fail!\n```\n\nInstead we must use the `sigs::Use\u003c\u003e::overloadOf()` construct:\n\n```c++\ns.connect(\u0026amb, sigs::Use\u003cint, int\u003e::overloadOf(\u0026Ambiguous::foo));\ns(42, 48);\n\n/* Prints:\nAmbiguous::foo(int, int)\n*/\n```\n\nWithout changing the signal we can also connect the second overload `foo(int, float)`:\n\n```c++\n// This one only works because int can be coerced into float.\ns.connect(\u0026amb, sigs::Use\u003cint, float\u003e::overloadOf(\u0026Ambiguous::foo));\ns(12, 34);\n\n/* Prints:\nAmbiguous::foo(int, int)\nAmbiguous::foo(int, float)\n*/\n```\n\nReturn values\n=============\nIf slots have return values they can be gathered by triggering the signal with a function. But the argument type must be the same as the return type!\n\nThe following example adds together the integers from each connected slot:\n```c++\nsigs::Signal\u003cint()\u003e s;\ns.connect([] { return 1; });\ns.connect([] { return 2; });\ns.connect([] { return 3; });\n\nint sum = 0;\ns([\u0026sum](int retVal) { sum += retVal; });\n// sum is now = 1 + 2 + 3 = 6\n```\n\nSignal interface\n================\nWhen a signal is used in an abstraction one most often doesn't want it exposed directly as a public member since it destroys encapsulation. `sigs::Signal::interface()` can be used instead to only expose connect and disconnect methods of the signal - it is a `std::unique_ptr\u003csigs::Signal::Interface\u003e` wrapper instance.\n\nThe example shows a button abstraction where actions can easily be added or removed while preserving the encapsulation of the signal:\n```c++\nclass Button {\npublic:\n  void click()\n  {\n    clickSignal_();\n  }\n\n  [[nodiscard]] auto clickSignal()\n  {\n    return clickSignal_.interface();\n  }\n\nprivate:\n  sigs::Signal\u003cvoid()\u003e clickSignal_;\n};\n\nint main()\n{\n  Button btn;\n  btn.clickSignal()-\u003econnect([] { std::cout \u003c\u003c \"direct fn\" \u003c\u003c std::endl; });\n  btn.clickSignal()-\u003econnect([] { std::cout \u003c\u003c \"direct fn 2\" \u003c\u003c std::endl; });\n\n  auto conn = btn.clickSignal()-\u003econnect([] { std::cout \u003c\u003c \"you won't see me\" \u003c\u003c std::endl; });\n  conn-\u003edisconnect();\n\n  btn.click();\n  return 0;\n}\n```\n\nBlocking signals and slots\n==========================\nSometimes it is necessary to block a signal, and any recursive signals, from triggering. That is achieved through `sigs::Signal::setBlocked(bool)` and `sigs::Signal::blocked()`:\n```c++\nsigs::Signal\u003cvoid()\u003e s;\ns.connect([] { /* .. */ });\ns.connect([] { /* .. */ });\ns.setBlocked(true);\n\n// No slots will be triggered since the signal is blocked.\ns();\n```\n\nTo make things simpler, the `sigs::SignalBlocker` class utilizes the RAII idiom to block/unblock via its own scoped lifetime:\n```c++\nsigs::Signal\u003cvoid()\u003e s;\ns.connect([] { /* .. */ });\ns.connect([] { /* .. */ });\n\n{\n  sigs::SignalBlocker\u003cvoid()\u003e blocker(s);\n\n  // No slots will be triggered since the signal is blocked.\n  s();\n}\n\n// All connected slots are triggered since the signal is no longer blocked.\ns();\n```\n\nCustomizing lock and mutex types\n================================\n\nThe default signal type `sigs::Signal\u003cT\u003e` is actually short for `sigs::BasicSignal\u003cT, sigs::BasicLock\u003e` (with `sigs::BasicLock = std::lock_guard\u003cstd::mutex\u003e`). Thus the lock type is `std::lock_guard` and the mutex type is `std::mutex`.\n\nCustom lock and mutex types can be supplied by defining a new type, for instance:\n```c++\ntemplate \u003ctypename T\u003e\nusing MySignal = sigs::BasicSignal\u003cT, MyLock\u003e;\n```\n\nThe required lock and mutex interfaces are as follows:\n```c++\nclass Mutex {\npublic:\n  void lock();\n  void unlock();\n};\n\ntemplate \u003ctypename Mutex\u003e\nclass Lock {\npublic:\n  using mutex_type = Mutex;\n\n  explicit Lock(Mutex \u0026);\n};\n```\n\nThe lock type is supposed to lock/unlock following the RAII idiom.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnetromdk%2Fsigs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnetromdk%2Fsigs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnetromdk%2Fsigs/lists"}