{"id":105754,"date":"2021-10-04T07:00:00","date_gmt":"2021-10-04T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105754"},"modified":"2024-08-16T16:48:06","modified_gmt":"2024-08-16T23:48:06","slug":"20211004-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211004-00\/?p=105754","title":{"rendered":"Some lesser-known powers of std::optional"},"content":{"rendered":"<p>C++17 introduced <code>std::optional&lt;T&gt;<\/code> which lets you augment the values of a type <code>T<\/code> with a bonus value known as <code>std::nullopt<\/code> which semantically represents the absence of a value. A <code>std::optional<\/code> which holds the value <code>std::nullopt<\/code> is known as <i>empty<\/i>.<\/p>\n<p>The basic operations on <code>std::optional<\/code> are<\/p>\n<ul>\n<li>Checking if it holds a value (<code>has_value()<\/code>)<\/li>\n<li>Retrieving the value (<code>value()<\/code>)<\/li>\n<li>Assigning a value (<code>=<\/code>)<\/li>\n<li>Clearing the value and returning to the empty state (<code>reset()<\/code>)<\/li>\n<\/ul>\n<p>There are other lesser-known powers of the <code>std::optional<\/code>.<\/p>\n<p><b>Contextual conversion<\/b><\/p>\n<p>If used in places where the language expects a Boolean (as the controlling expression for <code>if<\/code>, <code>while<\/code>, <code>for<\/code>, <code>?:<\/code>, or on either side of a <code>||<\/code> or <code>&amp;&amp;<\/code>), a <code>std::optional<\/code> is truthy if is has a value and falsy if it is empty.<\/p>\n<pre>if (opt)\r\n<\/pre>\n<p>is the same as<\/p>\n<pre>if (opt.has_value())\r\n<\/pre>\n<p>Note that this does not test whether the wrapped value is falsy.<\/p>\n<pre>std::optional&lt;bool&gt; opt1 = false;\r\nif (opt1) {\r\n    \/\/ this executes because the variable\r\n    \/\/ is non-empty (even though it is false)\r\n}\r\n\r\nstd::optional&lt;void*&gt; opt2 = nullptr;\r\nif (opt2) {\r\n    \/\/ this executes because the variable\r\n    \/\/ is non-empty (even though it is nullptr)\r\n}\r\n<\/pre>\n<p>My opinion: If <code>T<\/code> is itself contextually convertible to <code>bool<\/code>, write out <code>opt.has_value()<\/code> explicitly to avoid confusion.<\/p>\n<p><b>Equality comparison against a value<\/b><\/p>\n<p>An empty <code>std::optional&lt;T&gt;<\/code> compares unequal to any <code>T<\/code>.<\/p>\n<pre>std::optional&lt;int&gt; opt;\r\nif (opt == 0) {\r\n    \/\/ does not execute because the variable is empty\r\n    \/\/ and is not equal to any integer.\r\n}\r\n<\/pre>\n<p>My opinion: Use this instead of the more verbose <code>if (opt.has_value() &amp;&amp; opt.value() == 0)<\/code>.<\/p>\n<p><b>Ordering comparison against a value<\/b><\/p>\n<p>An empty <code>std::optional<\/code> compares less than any non-empty <code>std::optional<\/code>, and also less than any value.<\/p>\n<pre>std::optional&lt;int&gt; opt;\r\nif (opt &gt; 0) {\r\n    \/\/ does not execute because \"empty\" is\r\n    \/\/ less than all values\r\n}\r\n<\/pre>\n<p>My opinion: Avoid except when sorting, because this behavior differs from <code>NaN<\/code> (another popular &#8220;There&#8217;s nothing useful here&#8221; value) in that the corresponding opposite-sense test <i>does<\/i> execute.<\/p>\n<pre>if (opt &lt;= 0) {\r\n    \/\/ executes because \"empty\" is less than all values\r\n}\r\n<\/pre>\n<p>Instead, write it out as<\/p>\n<pre>    if (opt.has_value() &amp;&amp; *opt &gt; 0)\r\n    \/\/ or\r\n    if (opt.has_value() &amp;&amp; *opt &lt; 0)\r\n<\/pre>\n<p>Note that <code>opt.value()<\/code> and <code>*opt<\/code> both return the wrapped value but have different failure modes. The explicit <code>opt.value()<\/code> call will throw a <code>std::<wbr \/>bad_<wbr \/>optional_<wbr \/>access<\/code> exception if the object is empty, whereas the <code>*opt<\/code> bypasses the verification and you get undefined behavior if the object turns out to be empty after all. In the above case, you can write the code equivalent as<\/p>\n<pre>    if (opt.has_value() &amp;&amp; opt.value() &gt; 0)\r\n    \/\/ or\r\n    if (opt.has_value() &amp;&amp; opt.value() &lt; 0)\r\n<\/pre>\n<p>because the compiler can optimize out the redundant emptiness test.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Things it does that you may not know about.<\/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-105754","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Things it does that you may not know about.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105754","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=105754"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105754\/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=105754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}