{"id":111647,"date":"2025-10-02T07:00:00","date_gmt":"2025-10-02T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111647"},"modified":"2025-10-02T14:36:41","modified_gmt":"2025-10-02T21:36:41","slug":"20251002-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251002-00\/?p=111647","title":{"rendered":"The problem with inferring from a function call operator is that there may be more than one"},"content":{"rendered":"<p>Some time ago, I wrote briefly on <a title=\"Writing a helper class for generating a particular category of C callback wrappers around C++ methods\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250616-00\/?p=111271\"> writing a helper class for generating a particular category of C callback wrappers around C++ methods<\/a>. This particular mechanism used a proxy object with a templated conversion operator to figure out what function pointer type it was being asked to produce.\u00b9<\/p>\n<p>But what about taking a <code>std::invoke<\/code>&#8216;able object and inferring the function pointer from the parameters that the <code>invoke<\/code>&#8216;able&#8217;s <code>operator()<\/code> accepts?<\/p>\n<p>Sure, you could try to do that, but there&#8217;s a catch: There might be more than one <code>operator()<\/code>.<\/p>\n<p>The common case of this is a lambda with <code>auto<\/code> parameters.<\/p>\n<pre>RegisterCallback(\r\n    CallableWrapper([](auto first, auto second, auto third) {\r\n        \u27e6 ... \u27e7\r\n    }, context);\r\n<\/pre>\n<p>Formally, the lambda is a class with a templated function call operator.<\/p>\n<pre>class Lambda\r\n{\r\n    template&lt;typename T1, typename T2, typename T3&gt;\r\n    auto operator()(T1 first, T2 second, T3 third) {\r\n        \u27e6 ... \u27e7\r\n    }\r\n};\r\n<\/pre>\n<p>You can&#8217;t ask for the parameter types for the <code>operator()<\/code> because this class has infinitely many <code>operator()<\/code>s!<\/p>\n<p>In practice, people often use <code>auto<\/code> not because they want an infinite number of <code>operator()<\/code> methods but because they just don&#8217;t want to be bothered writing out the data types.<\/p>\n<p>In practice, when a library requires a callback function, it typically also defines a type name for the callback function signature, so specifying the callback function signature is normally not that onerous. (But we managed to avoid even that by deducing the callback function signature from the proxy.)<\/p>\n<p>While I&#8217;m here, I may as well expand the <code>Callback\u00adWrapper<\/code> so that it takes normal function pointers, too.<\/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\ntemplate&lt;typename F&gt; struct FunctionTraits;\r\n\r\ntemplate&lt;typename T, typename Arg1, typename... Rest&gt;\r\nstruct FunctionTraits&lt;T(*)(Arg1, Rest...)&gt;\r\n{\r\n    using First = Arg1;\r\n}\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        using FT = decltype(F);\r\n        if constexpr (std::is_member_function_pointer_v&lt;FT&gt;) {\r\n            auto obj = (typename MemberFunctionTraits&lt;FT&gt;::Object*)p;\r\n            return (obj-&gt;*F)((Args)args...); }\r\n        } else {\r\n            <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto obj = (typename FunctionTraits&lt;FT&gt;::First*)p;<\/span>\r\n            <span style=\"border: solid 1px currentcolor; border-top: none;\">return F(obj, (Args)args...);                     <\/span>\r\n        }\r\n    }\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<\/pre>\n<p>\u00b9 A danger of proxy objects with conversion operators is that people might not convert it right away. <a title=\"Of what use is a type-dependent expression that is always false?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200312-00\/?p=103556\"> As I&#8217;ve noted before<\/a>, the C++ language defines a specialization <code>std::vector&lt;bool&gt;<\/code> which represents a packed bit array, rather than defining a separate type like <code>std::bitvector<\/code>. This has made a lot of people very angry and <a href=\"https:\/\/isocpp.org\/blog\/2012\/11\/on-vectorbool\"> has been widely regarded<\/a> as a <a href=\"http:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2005\/n1847.pdf\"> bad move<\/a>.<\/p>\n<p>One of the unfortunate features of <code>std::vector&lt;bool&gt;<\/code> is that the <code>[]<\/code> operator does not produce a reference to a <code>bool<\/code>. (The <code>std::vector&lt;bool&gt;<\/code> doesn&#8217;t even have any <code>bool<\/code>s to return a reference to.) Instead, it returns a proxy object: If you convert the proxy object to a <code>bool<\/code>, then it reads a bit from the <code>std::vector&lt;bool&gt;<\/code>. If you assign a <code>bool<\/code> to the proxy object, then it sets\/clears a bit in the <code>std::vector&lt;bool&gt;<\/code>.<\/p>\n<p>But what if you don&#8217;t do either of those things?<\/p>\n<pre>extern std::vector&lt;bool&gt; GetBoolVector();\r\n\r\nvoid oops()\r\n{\r\n    auto firstBit = GetBoolVector()[0];\r\n    if (firstBit &amp;&amp; SomeOtherCondition()) {\r\n    }\r\n        \u27e6 ... \u27e7\r\n}\r\n<\/pre>\n<p>The <code>firstBit<\/code> is neither immediately assigned to nor converted. It&#8217;s just holds the potential of accessing the <code>vector&lt;bool&gt;<\/code>, and that potential is not realized until you do one of the two operations. In our case, we convert it to a <code>bool<\/code>, but it&#8217;s too late: The <code>vector&lt;bool&gt;<\/code> which it references no longer exists.<\/p>\n<pre>ERROR: AddressSanitizer: heap-use-after-free on address 0x77bab73e0010\r\n    at pc 0x000000401722 bp 0x7ffcd6143bf0 sp 0x7ffcd6143be8\r\nREAD of size 8 at 0x77bab73e0010 thread T0\r\n    #0 0x000000401721 in std::_Bit_reference::operator bool() const stl_bvector.h:106\r\n    #1 0x00000040157f in oops() oops.cpp:17\r\n    #2 0x0000004015f2 in main oops.cpp:22\r\n<\/pre>\n<p>Fortunately, our <code>CallbackWrapperMaker<\/code> doesn&#8217;t hold any references to other objects, so the proxy can be held indefinitely and converted when finally needed.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><CODE>auto<\/CODE> parameters make this easy to write, particularly for lambdas.<\/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-111647","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p><CODE>auto<\/CODE> parameters make this easy to write, particularly for lambdas.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111647","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=111647"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111647\/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=111647"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111647"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111647"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}