{"id":103995,"date":"2020-07-21T07:00:00","date_gmt":"2020-07-21T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103995"},"modified":"2020-07-21T11:52:55","modified_gmt":"2020-07-21T18:52:55","slug":"20200721-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200721-00\/?p=103995","title":{"rendered":"Deconstructing function pointers in a C++ template, returning to enable_if"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200720-28\/?p=103993\"> Last time<\/a>, we finally figured out a way to get partial specializations of function pointer traits to work with various calling conventions, even on systems where the calling conventions end up collapsing into the same underlying convention. The solution involved redirecting the unwanted duplicates to a non-default template parameter.<\/p>\n<p>I noted that our second attempt at <code>enable_if<\/code> didn&#8217;t work because the parameter to <code>enable_if<\/code> was always false, and therefore the result was always invalid. But what if we could make it a dependent type, so that the fact that it wasn&#8217;t invalid wasn&#8217;t immediately recognized?<\/p>\n<p>We saw this before, when we wanted to <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200311-00\/?p=103553\"> create a type-dependent expression that is always false<\/a>, and that led to our invention of <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200413-00\/?p=103669\"> <code>unconditional_t<\/code> and <code>unconditional_v<\/code><\/a>. We can dust those off and use them here.<\/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      unconditional_v&lt;bool,\r\n        !std::is_same_v&lt;\r\n          CallingConventions::Cdecl,\r\n          CallingConventions::Stdcall&gt;,\r\n        R&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 time, the use of <code>enable_if<\/code> works because there is a substitution going on: The substitution of <code>R<\/code> into <code>unconditional_v<\/code>. Now, it turns out that the substitution is meaningless, but the SFINAE rule doesn&#8217;t care about meaningfulness.<\/p>\n<p>Which leads us to the following version. I removed the dependency on <code>unconditional_v<\/code> by using the <code>sizeof(std::decay_t&lt;T&gt;*)<\/code> trick.<\/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, typename = void&gt;\r\nstruct FunctionTraits;\r\n\r\n#define MAKE_TRAITS2(CC, CCName, Noexcept, ArgList)     \\\r\ntemplate&lt;typename R, typename... Args&gt;                  \\\r\nstruct FunctionTraits&lt;R(CC*)ArgList noexcept(Noexcept), \\\r\n      std::enable_if_t&lt;sizeof(std::decay_t&lt;R&gt;*) &amp;&amp;      \\\r\n        !std::is_same_v&lt;CallingConventions::Cdecl,      \\\r\n                        CallingConventions::CCName&gt;&gt;&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(CC, CCName) \\\r\n        MAKE_TRAITS2(CC, CCName, true, (Args...)); \\\r\n        MAKE_TRAITS2(CC, CCName, true, (Args..., ...)); \\\r\n        MAKE_TRAITS2(CC, CCName, false, (Args...)); \\\r\n        MAKE_TRAITS2(CC, CCName, false, (Args..., ...))\r\n\r\nMAKE_TRAITS(CC_CDECL, Cdecl);\r\nMAKE_TRAITS(CC_STDCALL, Stdcall);\r\nMAKE_TRAITS(CC_FASTCALL, Fastcall);\r\nMAKE_TRAITS(CC_VECTORCALL, Vectorcall);\r\nMAKE_TRAITS(CC_REGCALL, Regcall);\r\n\r\n#undef MAKE_TRAITS\r\n#undef MAKE_TRAITS2\r\n<\/pre>\n<p>Oh this comes so very close, but we do get redefinition errors if three or more calling conventions collapse. That&#8217;s because the second and third ones generate identical partial specializations. The partial specializations are both ineffective because the <code>enable_if<\/code> fails, but the compiler doesn&#8217;t know that.<\/p>\n<p>There&#8217;s another problem because this code sees that <code>Cdecl<\/code> is the same as itself, so it removes the partial specialization for <code>Cdecl<\/code>, even though we want that one to hang around.<\/p>\n<p>We can rescue this by reintroducing the uniquifier, so that each <code>enable_if<\/code> is different and therefore doesn&#8217;t get counted as a duplicate. We also use the uniquifier to identify which expansion is for <code>cdecl<\/code> so we keep that one enabled.<\/p>\n<pre>#define MAKE_TRAITS2(U, CC, CCName, Noexcept, ArgList)  \\\r\ntemplate&lt;typename R, typename... Args&gt;                  \\\r\nstruct FunctionTraits&lt;R(CC*)ArgList noexcept(Noexcept), \\\r\n      std::enable_if_t&lt;sizeof(R*) &amp;&amp;                    \\\r\n        (U == 0 ||                                      \\\r\n         !std::is_same_v&lt;CallingConventions::Cdecl,     \\\r\n                        CallingConventions::CCName&gt;)&gt;&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(U, CC, CCName) \\\r\n        MAKE_TRAITS2(U, CC, CCName, true, (Args...)); \\\r\n        MAKE_TRAITS2(U, CC, CCName, true, (Args..., ...)); \\\r\n        MAKE_TRAITS2(U, CC, CCName, false, (Args...)); \\\r\n        MAKE_TRAITS2(U, 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<\/pre>\n<p>Phew, that was quite an ordeal. Trust me, this&#8217;ll come in handy someday. I just had to spend a bunch of time laying groundwork. The payoff will come some time later.<\/p>\n<p><b>Bonus chatter<\/b>: Even if you&#8217;re not interested in function pointer traits, you can use this technique for other scenarios where you want to specialize for a variety of types, some of which might be the same.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Going back to the old standby to filter out unwanted specializations.<\/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-103995","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Going back to the old standby to filter out unwanted specializations.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103995","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=103995"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103995\/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=103995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}