{"id":102678,"date":"2019-07-10T07:00:00","date_gmt":"2019-07-10T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102678"},"modified":"2019-07-10T23:15:12","modified_gmt":"2019-07-11T06:15:12","slug":"20190710-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190710-00\/?p=102678","title":{"rendered":"Detecting in C++ whether a type is defined, part 3: SFINAE and incomplete types"},"content":{"rendered":"<p><b>Warning to those who got here via a search engine<\/b>: This is part of a series. Keep reading to the end.<\/p>\n<p>For the past few articles, I&#8217;ve been playing with the <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/unqualified_lookup\"> unqualified name lookup<\/a> search order in order to detect whether a type exists in a particular namespace. I did this by defining the type in another namespace that has lower priority than the namespace that I&#8217;m probing, and then seeing which type comes out when I access the type with an unqualified name.<\/p>\n<p>There are many problems with this technique. One is that it requires you to set up a <code>detect<\/code> namespace that contains a shadow version of every type you want to check. Another is that you need to inject a <code>detect<\/code> sub-namespace into every namespace you want to do detection in.<\/p>\n<p>But it turns out there&#8217;s another way, as long as you&#8217;re willing to change one of the requirements. Instead of checking whether the type exists, check whether the type is <i>defined<\/i>, which in C++ language standard jargon means that you want the type to be <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/type#incomplete_type\"> complete<\/a>.<\/p>\n<pre>template&lt;typename, typename = void&gt;\r\nconstexpr bool is_type_complete_v = false;\r\n\r\ntemplate&lt;typename T&gt;\r\nconstexpr bool is_type_complete_v\r\n    &lt;T, std::void_t&lt;decltype(sizeof(T))&gt;&gt; = true;\r\n<\/pre>\n<p>A type must be complete in order to have the <code>sizeof<\/code> operator applied to it, so we use <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/sfinae\"> SFINAE<\/a> to define <code>is_<\/code><code>type_<\/code><code>complete_v<\/code> as <code>true<\/code> provided the <code>sizeof<\/code> operator can be applied.\u00b9<\/p>\n<p>I&#8217;m not sure if this is technically legal, but all the compilers I tried seemed to be okay with it. It does lead to weird effects like this:<\/p>\n<pre>struct s; \/\/ incomplete type\r\nbool val1 = is_type_complete_v&lt;s&gt;; \/\/ false\r\nstruct s {}; \/\/ now it's complete\r\nbool val2 = is_type_complete_v&lt;s&gt;; \/\/ true\r\n<\/pre>\n<p>The second phase of the trick takes advantage of the fact that you are permitted to refer to a class with the <code>struct<\/code> or <code>class<\/code> prefix. This prefix is usually redundant, <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190419-00\/?p=102431\"> but not always<\/a>. It&#8217;s also how you declare a forward reference.<\/p>\n<p>The result is that you can say<\/p>\n<pre>is_type_complete_v&lt;struct special&gt;\r\n<\/pre>\n<p>to determine whether <code>struct special<\/code> has been defined.<\/p>\n<ol>\n<li>If it has been defined, then the type exists and is complete.<\/li>\n<li>If it has been declared but not defined, then the type exists and is incomplete.<\/li>\n<li>If it has been neither declared nor defined, the act of writing <code>struct special<\/code> serves as a declaration! This puts us back into case 2 above, and the type exists and is incomplete.<\/li>\n<\/ol>\n<p>So now our helper can be simplified to<\/p>\n<pre>template&lt;typename T, typename TLambda&gt;\r\nvoid call_if_defined(TLambda&amp;&amp; lambda)\r\n{\r\n  if constexpr (is_complete_type_v&lt;T&gt;) {\r\n    lambda(static_cast&lt;T*&gt;(nullptr));\r\n  }\r\n}\r\n<\/pre>\n<p>and you would use it like this:<\/p>\n<pre>void foo(Source source)\r\n{\r\n  call_if_defined&lt;struct special&gt;([&amp;](auto* p)\r\n  {\r\n    using special = std::decay_t&lt;decltype(*p)&gt;;\r\n    special::static_method();\r\n    auto s = source.try_get&lt;special&gt;();\r\n    if (s) s-&gt;something();\r\n  });\r\n}\r\n<\/pre>\n<p>We are using the same tricks that we introduced last time: Using a generic lambda to defer resolving the type until the lambda is invoked, using <code>if constexpr<\/code> to avoid invoking the lambda if the type is not defined, and reintroducing the name of the type by deriving it from the dummy parameter.<\/p>\n<p>There is a catch here: If you are probing for a type that is defined in a namespace that you imported via a <code>using<\/code> directive, and the type does not actually exist, then the <code>struct special<\/code> will declare an incomplete <code>struct special<\/code> in the <i>current<\/i> namespace.<\/p>\n<pre>\/\/ awesome.h\r\nnamespace awesome\r\n{\r\n  \/\/ might or might not contain\r\n  struct special { ... };\r\n}\r\n\r\n\/\/ your code\r\nnamespace app\r\n{\r\n  using namespace awesome;\r\n\r\n  void foo()\r\n  {\r\n    call_if_defined&lt;struct special&gt;([&amp;](auto* p)\r\n    {\r\n       ...\r\n    });\r\n  }\r\n}\r\n<\/pre>\n<p>If <code>special<\/code> is not defined, then the <code>struct special<\/code> in the <code>call_<\/code><code>if_<\/code><code>defined<\/code> will introduce an incomplete type called <code>app::<\/code><code>special<\/code>.<\/p>\n<p>Even more frustrating is that you cannot do this:<\/p>\n<pre>namespace app\r\n{\r\n  void foo()\r\n  {\r\n    call_if_defined&lt;struct <span style=\"color: red;\">awesome::special<\/span>&gt;([&amp;](auto* p)\r\n    {\r\n       ...\r\n    });\r\n  }\r\n}\r\n<\/pre>\n<p>You cannot forward-declare a type into a non-current namespace. (For one thing, the result is ambiguous: Is this trying to forward-declare <code>::awesome<\/code><code>::special<\/code>, or is it trying to forward-declare <code>::app<\/code><code>::awesome<\/code><code>::special<\/code>?)<\/p>\n<p>And of course there&#8217;s the annoyance of having to type the word <code>struct<\/code>.<\/p>\n<p>We can trade three annoyances for one. We&#8217;ll continue the investigation next time.<\/p>\n<p>\u00b9 The <code>std::<\/code><code>void_t<\/code> template type is <code>void<\/code> regardless of its template parameters. The template type exists specifically for SFINAE, so that the overload is removed from consideration if the template parameter ends up being invalid. In this case, what it means is that if <code>sizeof(T)<\/code> is invalid (which is the case if <code>T<\/code> is an incomplete type), then the <code>std::<\/code><code>void_t<\/code> fails substitution, and the rule disappears.<\/p>\n<p>You can think of it as <code>std::<\/code><code>void_<\/code><code>if_<\/code><code>valid_t<\/code>.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can give something a name without saying what it is.<\/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-102678","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can give something a name without saying what it is.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102678","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=102678"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102678\/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=102678"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102678"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102678"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}