{"id":103993,"date":"2020-07-20T07:00:00","date_gmt":"2020-07-20T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103993"},"modified":"2020-07-21T11:56:35","modified_gmt":"2020-07-21T18:56:35","slug":"20200720-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200720-00\/?p=103993","title":{"rendered":"Deconstructing function pointers in a C++ template, addressing the calling convention conundrum"},"content":{"rendered":"<p>Last time, we <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200717-00\/?p=103989\"> tried<\/a> to create a partial specialization for a template type that has a function pointer template parameter with a specific calling convention, but have the partial specialization disappear if the calling convention is identical to <code>cdecl<\/code>, so that the compiler won&#8217;t complain about a duplicate definition.<\/p>\n<p>I couldn&#8217;t make the partial specialization disappear, but I could try the next best thing: Move it to somewhere harmless.<\/p>\n<p>I can give the <code>FunctionTraits<\/code> a second template parameter. If the calling conventions are the same, then I use a different second template parameter so that the partial specializations end up different. But if the calling conventions are different, then I use the same second template parameter, so that the function signature is the thing that makes the partial specializations different.<\/p>\n<pre>template&lt;typename F<span style=\"color: blue;\">, int = 0<\/span>&gt;\r\nstruct FunctionTraits;\r\n\r\ntemplate&lt;typename R, typename... Args&gt;\r\nstruct FunctionTraits&lt;R(CC_CDECL*)(Args...)<span style=\"color: blue;\">, 0<\/span>&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::is_same_v&lt;\r\n        CallingConventions::Cdecl,\r\n        CallingConventions::Stdcall&gt; ? 1 : 0<\/span>&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>If <code>cdecl<\/code> and <code>stdcall<\/code> are different, then the two partial specializations are<\/p>\n<pre>struct FunctionTraits&lt;R(CC_CDECL*)(Args...), 0&gt;\r\n<\/pre>\n<p>and<\/p>\n<pre>struct FunctionTraits&lt;R(CC_STDCALL*)(Args...), 0&gt;\r\n<\/pre>\n<p>Since <code>cdecl<\/code> and <code>stdcall<\/code> are different, the two function pointer types are not the same, and we therefore are defining two different partial specializations.<\/p>\n<p>On the other hand, if <code>cdecl<\/code> and <code>stdcall<\/code> are the same, then the two partial specializations come out to<\/p>\n<pre>struct FunctionTraits&lt;R(*)(Args...), 0&gt;\r\n<\/pre>\n<p>and<\/p>\n<pre>struct FunctionTraits&lt;R(*)(Args...), 1&gt;\r\n<\/pre>\n<p>Since <code>cdecl<\/code> and <code>stdcall<\/code> are the same, the two function pointer types are identical, but the integers are different, so we are still defining two different partial specializations.<\/p>\n<p>Since the default value for the second template parameter is the integer <code>0<\/code>, the <code>, 1<\/code> partial specialization will never be used. But that&#8217;s okay, because it&#8217;s identical to the <code>, 0<\/code> specialization anyway.<\/p>\n<p>Repeat for the other calling conventions, using unique integers for each, and also repeat for the <code>noexcept<\/code> and variadic versions. This means we write out the same partial specialization a total of twenty times, each slightly different. We&#8217;ll use a macro to generate them.<\/p>\n<pre>#if defined(__GNUC__) || defined(__clang__)\r\n  #define CC_CDECL __attribute__((cdecl))\r\n  #define CC_STDCALL __attribute__((stdcall))\r\n  #define CC_FASTCALL __attribute__((fastcall))\r\n  #define CC_VECTORCALL __attribute__((vectorcall))\r\n  #if defined(__INTEL_COMPILER)\r\n    #define CC_REGCALL __attribute__((regcall))\r\n  #else\r\n    #define CC_REGCALL CC_CDECL\r\n  #endif\r\n#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)\r\n  #define CC_CDECL __cdecl\r\n  #define CC_STDCALL __stdcall\r\n  #define CC_FASTCALL __fastcall\r\n  #define CC_VECTORCALL __vectorcall\r\n  #if defined(__INTEL_COMPILER)\r\n    #define CC_REGCALL __regcall\r\n  #else\r\n    #define CC_REGCALL CC_CDECL\r\n  #endif\r\n#else\r\n  #define CC_CDECL\r\n  #define CC_STDCALL\r\n  #define CC_FASTCALL\r\n  #define CC_VECTORCALL\r\n  #define CC_REGCALL\r\n#endif\r\n\r\nstruct CallingConventions\r\n{\r\n    using Cdecl = void(CC_CDECL*)();\r\n    using Stdcall = void(CC_STDCALL*)();\r\n    using Fastcall = void(CC_FASTCALL*)();\r\n    using Vectorcall = void(CC_VECTORCALL*)();\r\n    using Regcall = void(CC_REGCALL*)();\r\n};\r\n\r\ntemplate&lt;typename R, typename... Args&gt;\r\nstruct FunctionTraitsBase\r\n{\r\n   using RetType = R;\r\n   using ArgTypes = std::tuple&lt;Args...&gt;;\r\n   static constexpr std::size_t ArgCount = sizeof...(Args);\r\n   template&lt;std::size_t N&gt;\r\n   using NthArg = std::tuple_element_t&lt;N, ArgTypes&gt;;\r\n};\r\n\r\ntemplate&lt;typename F, int = 0&gt;\r\nstruct FunctionTraits;\r\n\r\n#define MAKE_TRAITS2(Unique, CC, CCName, Noexcept, ArgList)   \\\r\ntemplate&lt;typename R, typename... Args&gt;                        \\\r\nstruct FunctionTraits&lt;R(CC*)ArgList noexcept(Noexcept),       \\\r\n      std::is_same_v&lt;CallingConventions::Cdecl,               \\\r\n                     CallingConventions::CCName&gt;              \\\r\n                     ? Unique : 0&gt;                            \\\r\n    : FunctionTraitsBase&lt;R, Args...&gt;                          \\\r\n{                                                             \\\r\n  using Pointer = R(CC*)ArgList noexcept(Noexcept);           \\\r\n  using CallingConvention = CallingConventions::CCName;       \\\r\n  constexpr static bool IsNoexcept = Noexcept;                \\\r\n  constexpr static bool IsVariadic =                          \\\r\n    !std::is_same_v&lt;void(*)ArgList, void(*)(Args...)&gt;;        \\\r\n}\r\n\r\n#define MAKE_TRAITS(Unique, CC, CCName) \\\r\n        MAKE_TRAITS2(Unique, CC, CCName, true, (Args...)); \\\r\n        MAKE_TRAITS2(Unique, CC, CCName, true, (Args..., ...)); \\\r\n        MAKE_TRAITS2(Unique, CC, CCName, false, (Args...)); \\\r\n        MAKE_TRAITS2(Unique, CC, CCName, false, (Args..., ...))\r\n\r\nMAKE_TRAITS(0, CC_CDECL, Cdecl);\r\nMAKE_TRAITS(1, CC_STDCALL, Stdcall);\r\nMAKE_TRAITS(2, CC_FASTCALL, Fastcall);\r\nMAKE_TRAITS(3, CC_VECTORCALL, Vectorcall);\r\nMAKE_TRAITS(4, CC_REGCALL, Regcall);\r\n\r\n#undef MAKE_TRAITS\r\n#undef MAKE_TRAITS2\r\n<\/pre>\n<p>Only the Intel compiler supports <code>regcall<\/code>, so for the other compilers, I treat it like <code>cdecl<\/code>, which lets the existing machinery kick in and remove <code>regcall<\/code> from the active partial specializations.<\/p>\n<p>We pull a sneaky trick here by assigning the unique value <code>0<\/code> to <code>cdecl<\/code>: The value of <code>0<\/code> is the &#8220;Please use it&#8221; value. The macro recognizes that <code>Cdecl<\/code> is the same as <code>cdecl<\/code> and switches to the uniquifier to render the specialization moot, but the provided uniquifier of zero is in fact the value that makes the specialization active.<\/p>\n<p>But wait, this isn&#8217;t the only way to solve the problem. Next time, I&#8217;ll rewind and try a different direction.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Finding a template metaprogramming trick that works.<\/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-103993","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Finding a template metaprogramming trick that works.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103993","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=103993"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103993\/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=103993"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103993"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103993"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}