{"id":103989,"date":"2020-07-17T07:00:00","date_gmt":"2020-07-17T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103989"},"modified":"2020-07-17T11:34:46","modified_gmt":"2020-07-17T18:34:46","slug":"20200717-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200717-00\/?p=103989","title":{"rendered":"Deconstructing function pointers in a C++ template, trying to address the calling convention conundrum"},"content":{"rendered":"<p>Last time, we tried to extend our traits class for function pointers so that it accept <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200716-00\/?p=103986\"> functions in all different calling conventions<\/a>. This worked out great, except that on some architectures, many of the calling conventions are actually identical, and this causes our various partial specializations to result in duplicate definitions, and the compiler gets mad, and everything falls apart.<\/p>\n<p>What we want to do is remove the partial specializations for calling conventions which are identical to cdecl. (In practice, it is just cdecl that the other calling conventions collapse into.)<\/p>\n<p>We start by detecting whether a particular specialization should be enabled.<\/p>\n<pre>struct CallingConventions\r\n{\r\n    using Cdecl = void(CC_CDECL*)();\r\n    using Stdcall = void(CC_STDCALL*)();\r\n\r\n    <span style=\"color: blue;\">template&lt;typename T&gt;\r\n    static constexpr bool IsCdecl = std::is_same_v&lt;Cdecl, T&gt;;\r\n\r\n    static constexpr bool HasStdcall = !IsCdecl&lt;Stdcall&gt;;<\/span>\r\n};\r\n<\/pre>\n<p>We check whether various calling conventions are present by seeing if a function declared with that convention is the same as a function declared with <code>cdecl<\/code>. (This is why I made the various calling conventions be represented by a function pointer type.)<\/p>\n<p>The usual way to remove things is to use <code>std::enable_if<\/code>, so let&#8217;s try that. Enable the specialization if <code>cdecl<\/code> and <code>stdcall<\/code> are different.<\/p>\n<pre>template&lt;typename F&gt; struct FunctionTraits;\r\n\r\ntemplate&lt;typename R, typename... Args&gt;\r\nstruct FunctionTraits&lt;R(CC_CDECL*)(Args...)&gt;\r\n    : FunctionTraitsBase&lt;R, Args...&gt;\r\n{\r\n  using Pointer = R(CC_CDECL*)(Args...);\r\n  using CallingConvention = CallingConventions::Cdecl;\r\n};\r\n\r\ntemplate&lt;typename R, typename... Args,\r\n    <span style=\"color: blue;\">typename = std::enable_if_t&lt;\r\n      !std::is_same_v&lt;\r\n        CallingConventions::Cdecl,\r\n        CallingConventions::Stdcall&gt;&gt;<\/span>\r\n    &gt;\r\nstruct FunctionTraits&lt;R(CC_STDCALL*)(Args...)&gt;\r\n    : FunctionTraitsBase&lt;R, Args...&gt;\r\n{\r\n  using Pointer = R(CC_STDCALL*)(Args...);\r\n  using CallingConvention = CallingConventions::Stdcall;\r\n};\r\n<\/pre>\n<p>This fails to compile. First we get &#8220;default template arguments may not be used in partial specialization&#8221; on the <code>typename = std::enable_if_t&lt;...&gt;<\/code>. This error message is self-explanatory.<\/p>\n<p>And then we get &#8220;redefinition of <code>FunctionTraits&lt;R(*)(Args...)&gt;<\/code>.&#8221; The problem is that in the case where <code>cdecl<\/code> and <code>stdcall<\/code> are the same, the partial specializations both try to define <code>FunctionTraits&lt;R(*)(Args...)&gt;<\/code>, so the compiler doesn&#8217;t know which one to choose.<\/p>\n<p>Okay, so let&#8217;s put the <code>enable_if<\/code> in the second part.<\/p>\n<pre>template&lt;typename F<span style=\"color: blue;\">, typename = void<\/span>&gt;\r\nstruct FunctionTraits;\r\n\r\ntemplate&lt;typename R, typename... Args&gt;\r\nstruct FunctionTraits&lt;R(CC_CDECL*)(Args...)&gt;\r\n    : FunctionTraitsBase&lt;R, Args...&gt;\r\n{\r\n  using Pointer = R(CC_CDECL*)(Args...);\r\n  using CallingConvention = CallingConventions::Cdecl;\r\n};\r\n\r\ntemplate&lt;typename R, typename... Args&gt;\r\nstruct FunctionTraits&lt;R(CC_STDCALL*)(Args...),\r\n    <span style=\"color: blue;\">std::enable_if_t&lt;\r\n      !std::is_same_v&lt;\r\n        CallingConventions::Cdecl,\r\n        CallingConventions::Stdcall&gt;&gt;<\/span>\r\n    &gt;\r\n    : FunctionTraitsBase&lt;R, Args...&gt;\r\n{\r\n  using Pointer = R(CC_STDCALL*)(Args...);\r\n  using CallingConvention = CallingConventions::Stdcall;\r\n};\r\n<\/pre>\n<p>This fails with &#8220;template argument 2 (<code>std::enable_if_t&lt;...&gt;<\/code>) is invalid.&#8221; This is a fair complaint, because it is indeed invalid. SFINAE does not apply here because there is no substitution going on. We flat-out passed an invalid type as a template parameter, and that&#8217;s an error.<\/p>\n<p>But this gave me an idea. The problem with the first attempt is that we ended up with two partial specializations that ended up being the same thing. The problem with the second attempt is that we tried to create a bad partial specialization.<\/p>\n<p>But maybe I can combine both bad ideas!<\/p>\n<p>We&#8217;ll try that next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hunting for the right template metaprogramming trick.<\/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-103989","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Hunting for the right template metaprogramming trick.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103989","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=103989"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103989\/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=103989"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103989"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103989"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}