{"id":112767,"date":"2026-10-12T07:00:00","date_gmt":"2026-10-12T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112767"},"modified":"2026-10-12T21:35:26","modified_gmt":"2026-10-13T04:35:26","slug":"20261012-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20261012-00\/?p=112767\/","title":{"rendered":"C++ futures part 1: The mental model"},"content":{"rendered":"<p>The C++ standard library has a group of related functions and classes: <code>std::promise<\/code>, <code>std::packaged_task<\/code>, <code>std::async<\/code>, <code>std::future<\/code>, and <code>std::shared<wbr \/>future<\/code>.<\/p>\n<p>The idea is that all of these classes are built of a single unnamed class which the C++ standard library calls the &#8220;state&#8221;, but for the purpose of discussion, I&#8217;m going to call it the <code>source<\/code>, since it is the thing that produces the value, and it behaves analogously to a C# task completion source.<\/p>\n<p>The <code>source<\/code> conceptually supports these operations:<\/p>\n<ul>\n<li>Set a value.<\/li>\n<li>Set an exception.<\/li>\n<li>Wait for a value or exception to be set.<\/li>\n<li>Retrieve the value or rethrow the exception.<\/li>\n<\/ul>\n<p>Here&#8217;s a simplified version that doesn&#8217;t support references.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct source\r\n{\r\nprivate:\r\n    enum what {\r\n        nothing,\r\n        value,\r\n        exception,\r\n        consumed,\r\n    } d; \/\/ discriminant\r\n\r\n    union {\r\n        T value;\r\n        std::exception_ptr ex;\r\n    } r; \/\/ result\r\n\r\n    std::mutex m;\r\n    std::condition_variable cv;\r\n<\/pre>\n<p>The discriminant tells us what is in the union: Nothing (because nobody has set anything), a value, an exception, or nothing (because the value has been consumed).<\/p>\n<p>The mutex ensures thread-safe access to the other members, and the condition variable lets us wait for somebody to set a value or exception.<\/p>\n<pre>    void must_be_empty() {\r\n        if (d != what::empty) {\r\n            throw std::future_error(future_errc::promise_already_satisfied);\r\n        }\r\n    }\r\n<\/pre>\n<p>You can call the <code>set<\/code> methods only when the source is empty, so we&#8217;ll factor out the emptiness check.<\/p>\n<pre>public:\r\n    void set_value(T&amp; v) {\r\n        std::lock_guard g(m);\r\n        must_be_empty();\r\n        new(&amp;r.value) T(v); \/\/ \"r.value = v\"\r\n        d = what::value;\r\n        cv.notify_all();\r\n    }\r\n<\/pre>\n<p>You can set a value or exception only once, so if there is already a value or exception, we yell at you. Otherwise, we save the value and notify anybody who was waiting for a value to be set.<\/p>\n<p>I won&#8217;t bother with the overload that takes <code>T&amp;&amp;<\/code>; it&#8217;s the same thing, but with a <code>std::move()<\/code>.<\/p>\n<p>There is also a <code>set_<wbr \/>value_<wbr \/>at_<wbr \/>thread_<wbr \/>exit<\/code> which will be another topic for another day.<\/p>\n<p>Instead of setting a value, you can set an exception:<\/p>\n<pre>    void set_exception(std::exception_ptr ex) {\r\n        std::lock_guard g(m);\r\n        must_be_empty();\r\n        new(&amp;r.ex) std::exception_ptr(ex); \/\/ \"r.ex = ex\"\r\n        d = what::exception;\r\n        cv.notify_all();\r\n    }\r\n<\/pre>\n<p>It&#8217;s basically the same as <code>set_<wbr \/>value<\/code> except that we store an exception.<\/p>\n<p>That&#8217;s how values an exceptions go <i>into<\/i> the source. The next part is waiting for the source to become nonempty.<\/p>\n<pre>    void wait() {\r\n        std::lock_guard g(m);\r\n        while (d == what::empty) {\r\n            cv.wait(g);\r\n        }\r\n    }\r\n\r\n    void wait_for(duration dur) {\r\n        std::lock_guard g(m);\r\n        cv.wait_for(g, dur, [&amp;] { return d != what::empty; });\r\n    }\r\n<\/pre>\n<p>You can either wait indefinitely or wait for a particular duration. The <code>wait_for<\/code> method is why we have a condition variable at all. If it weren&#8217;t for that guy, we could have used <code>std::<wbr \/>atomic<\/code>&#8216;s <code>wait<\/code> and <code>notify_<wbr \/>all<\/code> methods.<\/p>\n<p>The last part is getting the values out.<\/p>\n<pre>    void can_get() {\r\n        if (d == what::consumed) {\r\n            throw std::future_error(future_errc::future_already_retrieved);\r\n        }\r\n        if (d == what::exception) {\r\n            std::rethrow_exception(r.ex);\r\n        }\r\n    }\r\n<\/pre>\n<p>Regardless of how we intend to retrieve the value, it&#8217;s not legal to retrieve a value that has already been consumed. Also, if the result is an exception, we rethrow the exception.<\/p>\n<pre>    T consume() {\r\n        wait();\r\n        std::lock_guard g(m);\r\n        can_get();\r\n        d = what::consumed;\r\n        return std::move(r.v);\r\n    }\r\n\r\n    const T&amp; get() {\r\n        wait();\r\n        std::lock_guard g(m);\r\n        can_get();\r\n        return r.v;\r\n    }\r\n};\r\n<\/pre>\n<p>You can get the value, which returns a const reference to the underlying storage. Or you can consume the value, which moves the value out of the underlying storage and marks the value as consumed so nobody can consume or get it (because it&#8217;s not there any more).<\/p>\n<p>We can now build our other objects and functions out of this guy. We&#8217;ll do that next time.<\/p>\n<p><b>Bonus chatter<\/b>: Writing the destructor is left as an exercise for the reader. It&#8217;s not hard, but it&#8217;s annoying because of the union.<\/p>\n<p><b>Bonus bonus chatter<\/b>: I&#8217;m writing the series because I always forget how it works, but writing it down helps me remember.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Managing a value that is produced later.<\/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-112767","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Managing a value that is produced later.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112767","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=112767"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112767\/revisions"}],"predecessor-version":[{"id":112768,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112767\/revisions\/112768"}],"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=112767"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112767"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112767"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}