{"id":111271,"date":"2025-06-16T07:00:00","date_gmt":"2025-06-16T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111271"},"modified":"2025-06-17T15:27:44","modified_gmt":"2025-06-17T22:27:44","slug":"20250616-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250616-00\/?p=111271","title":{"rendered":"Writing a helper class for generating a particular category of C callback wrappers around C++ methods"},"content":{"rendered":"<p>A common pattern for C callbacks is to accept a function pointer and a <code>void*<\/code>, and then the callback function receives that pointer in addition to the parameters specific to the callback.<\/p>\n<pre>\/\/ Hypothetical callback\r\ntypedef int (*callback_t)(\r\n    void* context,\r\n    int arg1, char const* arg2, double arg3);\r\n\r\nvoid RegisterCallback(callback_t callback, void* context);\r\n<\/pre>\n<p>If you&#8217;re writing code in C++, it&#8217;s more convenient to write the callback as a class member function.<\/p>\n<pre>struct Widget\r\n{\r\n    int OnCallback(\r\n        int arg1, char const* arg2, double arg3);\r\n\r\n    static int OnCallbackStatic(void* context,\r\n        int arg1, char const* arg2, double arg3)\r\n    {\r\n        auto self = (Widget*)context;\r\n        return self-&gt;OnCallback(arg1, arg2, arg3);\r\n    }\r\n\r\n    void Register()\r\n    {\r\n        RegisterCallback(OnCallbackStatic, this);\r\n    }\r\n};\r\n<\/pre>\n<p>Is there a way to simplify this boilerplate?<\/p>\n<p>My idea was to use the conversion operator to deduce the callback function signature, and give the wrapper function the same signature. The wrapper function then forwards the parameters to the member function and propagates the result. Doing it this way means that the member function need not have the exact same signature, as long as the inputs and outputs are convertible.<\/p>\n<pre>template&lt;auto F&gt;\r\nstruct CallbackWrapperMaker\r\n{\r\n    template&lt;typename Ret, typename...Args&gt;\r\n    static Ret callback(void* p, Args...args)\r\n        { auto obj = (???*)p;\r\n          return (obj-&gt;*F)((Args)args...); }\r\n\r\n    template&lt;typename Ret, typename...Args&gt;\r\n    using StaticCallback = Ret(*)(void*, Args...);\r\n\r\n    template&lt;typename Ret, typename...Args&gt;\r\n    operator StaticCallback&lt;Ret, Args...&gt;()\r\n        { return callback&lt;Ret, Args...&gt;; }\r\n};\r\n\r\ntemplate&lt;auto F&gt;\r\ninline CallbackWrapperMaker&lt;F&gt; CallbackWrapper =\r\n    CallbackWrapperMaker&lt;F&gt;();\r\n\r\nstruct Widget\r\n{\r\n    int OnCallback(\r\n        int arg1, char const* arg2, double arg3);\r\n\r\n    void Register()\r\n    {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">RegisterCallback(                        <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    CallbackWrapper&lt;&amp;Widget::OnCallback&gt;,<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">    this);                               <\/span>\r\n    }\r\n};\r\n<\/pre>\n<p>\/\/ usage:<\/p>\n<p>The <code>CallbackWrapper<\/code> is an empty object whose job is merely to be convertible to a C-style callback function. We use the conversion operator to detect the signature that we need to produce, and we use those template type parameters to generate the correct version of the <code>callback<\/code> function.<\/p>\n<p>The part we haven&#8217;t written yet is the code to cast the context parameter <code>p<\/code> back to the original object type, like <code>Widget<\/code>. To do that, we use a helper traits type that can extract the parent object from a pointer to member function.<\/p>\n<pre>template&lt;typename F&gt; struct MemberFunctionTraits;\r\n\r\ntemplate&lt;typename Ret, typename T, typename...Args&gt;\r\nstruct MemberFunctionTraits&lt;Ret(T::*)(Args...)&gt;\r\n{\r\n    using Object = T;\r\n};\r\n\r\n\r\ntemplate&lt;auto F&gt;\r\nstruct CallbackWrapperMaker\r\n{\r\n    template&lt;typename Ret, typename...Args&gt;\r\n    static Ret callback(void* p, Args...args)\r\n        { auto obj = <span style=\"border: solid 1px currentcolor; border-bottom: none;\">(typename MemberFunctionTraits&lt;decltype(F)&gt;::<\/span>\r\n                     <span style=\"border: solid 1px currentcolor; border-top: none;\">            Object*)<\/span><span style=\"border-top: solid 1px currentcolor;\">p;                       <\/span>\r\n          return (obj-&gt;*F)((Args)args...); }\r\n\r\n    \u27e6 ... rest as before ... \u27e7\r\n};\r\n<\/pre>\n<p>The nice thing about forwarding to the member function is that the member function need not accept the parameters in the same way as the callback. For example, we could have written<\/p>\n<pre>struct Widget\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">short<\/span> OnCallback(\r\n        <span style=\"border: solid 1px currentcolor;\">long<\/span> arg1, char const* arg2, double arg3);\r\n\r\n    void Register()\r\n    {\r\n        RegisterCallback(\r\n            CallbackWrapper&lt;&amp;Widget::OnCallback&gt;,\r\n            this);\r\n    }\r\n};\r\n<\/pre>\n<p>and the compiler will automatically apply the normal integral promotions of <code>arg1<\/code> from <code>int<\/code> to <code>long<\/code> and the return value from <code>short<\/code> to <code>int<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Another exercise in C++ template programming.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-111271","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Another exercise in C++ template programming.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111271"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111271\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}