|
| 1 | +<?xml version='1.0' encoding='utf-8' standalone='no'?> |
| 2 | +<!DOCTYPE issue SYSTEM "lwg-issue.dtd"> |
| 3 | + |
| 4 | +<issue num="4509" status="New"> |
| 5 | +<title><tt>std::optional<T>::transform</tt> cannot be implemented while supporting program-defined specializations</title> |
| 6 | +<section><sref ref="[optional.monadic]"/></section> |
| 7 | +<submitter>Rasheeq Azad</submitter> |
| 8 | +<date>24 Dec 2025</date> |
| 9 | +<priority>99</priority> |
| 10 | + |
| 11 | +<discussion> |
| 12 | +<p> |
| 13 | +Currently (that is, as of the draft at <paper num="N5032"/>), <sref ref="[optional.monadic]"/> |
| 14 | +specifies that <tt>std::optional<T>::transform(F&&f)&</tt> shall do the following |
| 15 | +(and similar for the other overloads): |
| 16 | +</p> |
| 17 | +<blockquote style="border-left: 3px solid #ccc;padding-left: 15px;"> |
| 18 | +<p> |
| 19 | +Let `U` be <tt>remove_cv_t<invoke_result_t<F, decltype((<i>val</i>))>></tt>. |
| 20 | +<p/> |
| 21 | +<i>Mandates</i>: […] |
| 22 | +<p/> |
| 23 | +[<i>Note 1</i>: There is no requirement that `U` is movable (<sref ref="[dcl.init.general]"/>). |
| 24 | +— <i>end note</i>] |
| 25 | +<p/> |
| 26 | +<i>Returns</i>: If `*this` contains a value, an <tt>optional<U></tt> object whose |
| 27 | +contained value is direct-non-list-initialized with <tt>invoke(std::forward<F>(f), <i>val</i>)</tt>; |
| 28 | +otherwise, <tt>optional<U>()</tt>. |
| 29 | +</p> |
| 30 | +</blockquote> |
| 31 | +<p> |
| 32 | +However, none of the standard constructors or other member functions of |
| 33 | +<tt>optional<U></tt> provide a surefire way to initialize the contained `U` value with |
| 34 | +an expression like <tt>invoke(std::forward<F>(f), <i>val</i>)</tt>. The closest are the |
| 35 | +`in_place_t`/`emplace` overloads, which almost but not quite admit a generic |
| 36 | +implementation of `transform`. This looks roughly like: |
| 37 | +</p> |
| 38 | +<blockquote><pre> |
| 39 | +namespace std { |
| 40 | + template<class _F> struct __later { |
| 41 | + _F __f; |
| 42 | + operator decltype(std::move(__f)())() && { return std::move(__f)(); } |
| 43 | + }; |
| 44 | + |
| 45 | + template<class _T> class optional { |
| 46 | + // etc. |
| 47 | + public: |
| 48 | + template<class _F> constexpr auto transform(_F &&__f) & { |
| 49 | + using _U = remove_cv_t<invoke_result_t<_F, _T&>>; |
| 50 | + if(!has_value()) return optional<_U>(); |
| 51 | + return optional<_U>(in_place, __later([&] -> _U { |
| 52 | + return std::invoke(std::forward<_F>(__f), value()); |
| 53 | + })); |
| 54 | + } |
| 55 | + }; |
| 56 | +} |
| 57 | +</pre></blockquote> |
| 58 | +<p> |
| 59 | +Unfortunately, this does not quite meet the specification. The issue is if `U` |
| 60 | +is a type with a <tt>U(auto&&)</tt> constructor: |
| 61 | +</p> |
| 62 | +<blockquote><pre> |
| 63 | +struct oops { |
| 64 | + oops() = default; |
| 65 | + oops(auto&&) { std::cout << "launching missiles\n"; } |
| 66 | +}; |
| 67 | + |
| 68 | +int main() { |
| 69 | + std::optional<int> oi(5); |
| 70 | + oi.transform([](auto& i) { return oops(); }); |
| 71 | + // missiles get launched when they shouldn't |
| 72 | +} |
| 73 | +</pre></blockquote> |
| 74 | +<p> |
| 75 | +In this case, the rules for direct-initialization (see <sref ref="[dcl.init]"/> bullet 16.6.2) |
| 76 | +will select the template constructor over the conversion function on the `__later` |
| 77 | +specialization. [<a href="https://godbolt.org/z/G6vjM4K63">Complete example 1</a>] |
| 78 | +<p/> |
| 79 | +To avoid this problem, standard library implementors generally implement |
| 80 | +<tt>std::optional<T>::transform</tt> with a non-standard constructor on their |
| 81 | +<tt>std::optional<T></tt> primary template; roughly: |
| 82 | +</p> |
| 83 | +<blockquote><pre> |
| 84 | +namespace std { |
| 85 | + struct __optional_from_invocable_tag { |
| 86 | + constexpr explicit __optional_from_invocable_tag() { } |
| 87 | + }; |
| 88 | + |
| 89 | + template<typename _T> |
| 90 | + class optional { |
| 91 | + // etc. |
| 92 | + public: |
| 93 | + template<typename _F, typename _V> |
| 94 | + constexpr optional(__optional_from_invocable_tag, _F &&__f, _V &&__v) |
| 95 | + : __present(true) |
| 96 | + , __val(std::invoke(std::forward<_F>(__f), std::forward<_V>(__v))) |
| 97 | + { } |
| 98 | + |
| 99 | + template<class _F> constexpr auto transform(_F &&__f) & { |
| 100 | + using _U = remove_cv_t<invoke_result_t<_F, _T&>>; |
| 101 | + if(!has_value()) return optional<_U>(); |
| 102 | + return optional<_U>( |
| 103 | + __optional_from_invocable_tag(), |
| 104 | + std::forward<_F>(__f), value()); |
| 105 | + } |
| 106 | + }; |
| 107 | +} |
| 108 | +</pre></blockquote> |
| 109 | +<p> |
| 110 | +[<a href="https://godbolt.org/z/5qsnesbdh">Complete example 2</a>]. Note that the missiles are not launched. |
| 111 | +<p/> |
| 112 | +Now for the real issue: if a user program wants to specialize `std::optional` |
| 113 | +for a program-defined type, it will have to explicitly rely on these details of |
| 114 | +its standard library implementation in order to be supported by the standard |
| 115 | +library's `transform` implementation. Specifically, it will have to provide a |
| 116 | +non-standard constructor with a signature matching the library implementation's |
| 117 | +expectations. (A portable implementation of `transform` itself is more-or-less |
| 118 | +possible for a program-defined specialization by using a circumlocution like |
| 119 | +<tt>std::optional<std::monostate>(std::in_place).transform(/* ... */)</tt>.) |
| 120 | +<p/> |
| 121 | +The root problem is that the standard interface of <tt>std::optional<U></tt> |
| 122 | +provides for direct-initialization of the contained `U` by arbitrary glvalues, but not |
| 123 | +by an arbitrary prvalue (that is, by calling an arbitrary invocable). This |
| 124 | +forces library implementations to invent their own non-standard interfaces for |
| 125 | +doing so, which then makes it impossible for those implementations to support |
| 126 | +program-defined specializations of `std::optional` that only meet the minimal |
| 127 | +requirements of the standard, and do not support those non-standard interfaces. |
| 128 | +<p/> |
| 129 | +The fact that <tt>std::optional<T>::transform</tt> makes implementing `std::optional` |
| 130 | +while supporting program-defined specializations basically impossible does not |
| 131 | +appear to be intentional. <paper num="P0798R8"/>, which introduced |
| 132 | +<tt>std::optional<T>::transform</tt>, does not mention this side-effect of its |
| 133 | +standardization. |
| 134 | +<p/> |
| 135 | +There are at least two different resolutions that immediately come to mind. |
| 136 | +<p/> |
| 137 | +<b>Option A</b>: Forbid program-defined <tt>std::optional<T></tt> specializations |
| 138 | +<p/> |
| 139 | +Taking this option would immediately solve the problem. However, in my opinion, |
| 140 | +this would be unnecessarily restrictive. Specializing `std::optional` is a |
| 141 | +useful thing to allow, as it allows replacing the common <tt>struct optional<T> { |
| 142 | +union { T val; }; bool present; }</tt> representation with something more compact |
| 143 | +when `T` has unused values/unused bits. |
| 144 | +<p/> |
| 145 | +<b>Option B</b>: Add a <tt>std::optional<T></tt> constructor taking an invocable |
| 146 | +<p/> |
| 147 | +This option more-or-less formalizes existing practice, using a type tag to gate |
| 148 | +the new constructor. It would be ideal to extend this idea to `emplace` and |
| 149 | +then to the various `in_place_t` constructors and `emplace` functions in other |
| 150 | +parts of the standard, but the wording presented here is restricted to fixing |
| 151 | +this issue. |
| 152 | +<p/> |
| 153 | +Changing <tt>std::optional<T&></tt> doesn't seem strictly necessary, |
| 154 | +but introducing a nonuniformity seems like a bad idea. I'm not 100% certain about |
| 155 | +the wording for the new constructors. |
| 156 | +</p> |
| 157 | +</discussion> |
| 158 | + |
| 159 | +<resolution> |
| 160 | +<p> |
| 161 | +This wording is relative to <paper num="N5032"/>. |
| 162 | +</p> |
| 163 | + |
| 164 | +<blockquote class="note"> |
| 165 | +<p> |
| 166 | +[<i>Drafting Note:</i> Two mutually exclusive options are prepared, depicted below by <b>Option A</b> and |
| 167 | +<b>Option B</b>, respectively.] |
| 168 | +</p> |
| 169 | +</blockquote> |
| 170 | + |
| 171 | +<p> |
| 172 | +<b>Option A</b>: Forbid program-defined <tt>std::optional<T></tt> specializations |
| 173 | +</p> |
| 174 | + |
| 175 | +<ol> |
| 176 | +<li><p>Modify <sref ref="[optional.optional.general]"/> as indicated:</p> |
| 177 | + |
| 178 | +<blockquote> |
| 179 | +<p> |
| 180 | +-2- A type `X` is a <i>valid contained type</i> for `optional` […]. If `T` is an object type, |
| 181 | +`T` shall meet the <i>Cpp17Destructible</i> requirements (Table 35). |
| 182 | +<p/> |
| 183 | +<ins>-?- The behavior of a program that adds a specialization for `optional` is undefined.</ins> |
| 184 | +</p> |
| 185 | +</blockquote> |
| 186 | +</li> |
| 187 | + |
| 188 | +</ol> |
| 189 | + |
| 190 | +<p> |
| 191 | +<b>Option B</b>: Add a <tt>std::optional<T></tt> constructor taking an invocable |
| 192 | +</p> |
| 193 | + |
| 194 | +<ol> |
| 195 | +<li><p>Modify <sref ref="[utility.syn]"/>, header <tt><utility></tt> synopsis, as indicated:</p> |
| 196 | + |
| 197 | +<blockquote> |
| 198 | +<pre> |
| 199 | +[…] |
| 200 | +namespace std { |
| 201 | + […] |
| 202 | + template<size_t I> |
| 203 | + struct in_place_index_t { |
| 204 | + explicit in_place_index_t() = default; |
| 205 | + }; |
| 206 | + template<size_t I> constexpr in_place_index_t<I> in_place_index{}; |
| 207 | + |
| 208 | + <ins><i>// construction from arbitrary initializers</i></ins> |
| 209 | + |
| 210 | + <ins>struct from_continuation_t { |
| 211 | + explicit from_continuation_t() = default; |
| 212 | + }; |
| 213 | + inline constexpr from_continuation_t from_continuation{};</ins> |
| 214 | + […] |
| 215 | +} |
| 216 | +</pre> |
| 217 | +</blockquote> |
| 218 | +</li> |
| 219 | + |
| 220 | +<li><p>Modify <sref ref="[optional.optional.general]"/> as indicated:</p> |
| 221 | + |
| 222 | +<blockquote> |
| 223 | +<pre> |
| 224 | +namespace std { |
| 225 | + template<class T> |
| 226 | + class optional { |
| 227 | + public: |
| 228 | + […] |
| 229 | + <i>// <sref ref="[optional.ctor]"/>, constructors</i> |
| 230 | + constexpr optional() noexcept; |
| 231 | + constexpr optional(nullopt_t) noexcept; |
| 232 | + […] |
| 233 | + template<class... Args> |
| 234 | + constexpr explicit optional(in_place_t, Args&&...); |
| 235 | + template<class U, class... Args> |
| 236 | + constexpr explicit optional(in_place_t, initializer_list<U>, Args&&...); |
| 237 | + <ins>template<class F, class... Args> |
| 238 | + constexpr explicit optional(from_continuation_t, F&&, Args&&...);</ins> |
| 239 | + template<class U = remove_cv_t<T>> |
| 240 | + constexpr explicit(<i>see below</i>) optional(U&&); |
| 241 | + […] |
| 242 | + }; |
| 243 | + […] |
| 244 | +} |
| 245 | +</pre> |
| 246 | +</blockquote> |
| 247 | +</li> |
| 248 | + |
| 249 | +<li><p>Modify <sref ref="[optional.ctor]"/> as indicated:</p> |
| 250 | + |
| 251 | +<blockquote> |
| 252 | +<pre> |
| 253 | +template<class U, class... Args> |
| 254 | + constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args); |
| 255 | +</pre> |
| 256 | +<blockquote> |
| 257 | +<p> |
| 258 | +-18- <i>Constraints</i>: […] |
| 259 | +<p/> |
| 260 | +[…] |
| 261 | +<p/> |
| 262 | +-22- <i>Remarks</i>: If `T`'s constructor selected for the initialization is a constexpr constructor, |
| 263 | +this constructor is a constexpr constructor. |
| 264 | +</p> |
| 265 | +</blockquote> |
| 266 | +<pre> |
| 267 | +<ins>template<class F, class... Args> |
| 268 | + constexpr explicit optional(from_continuation_t, F&& f, Args&&... args);</ins> |
| 269 | +</pre> |
| 270 | +<blockquote> |
| 271 | +<p> |
| 272 | +<ins>-?- <i>Mandates</i>: <tt>decltype(std::invoke(std::forward<F>(f), std::forward<Args>(args)...))</tt> is `T`.</ins> |
| 273 | +<p/> |
| 274 | +<ins>-?- <i>Effects</i>: Direct-non-list-initializes <tt><i>val</i></tt> with |
| 275 | +<tt>std::invoke(std::forward<F>(f), std::forward<Args>(args)...)</tt>.</ins> |
| 276 | +<p/> |
| 277 | +<ins>-?- <i>Postconditions</i>: `*this` contains a value.</ins> |
| 278 | +</p> |
| 279 | +</blockquote> |
| 280 | +</blockquote> |
| 281 | +</li> |
| 282 | + |
| 283 | +<li><p>Modify <sref ref="[optional.optional.ref.general]"/> as indicated:</p> |
| 284 | + |
| 285 | +<blockquote> |
| 286 | +<pre> |
| 287 | +namespace std { |
| 288 | + template<class T> |
| 289 | + class optional<T&> { |
| 290 | + public: |
| 291 | + […] |
| 292 | + <i>// <sref ref="[optional.ref.ctor]"/>, constructors</i> |
| 293 | + constexpr optional() noexcept = default; |
| 294 | + constexpr optional(nullopt_t) noexcept : optional() {} |
| 295 | + […] |
| 296 | + template<class Arg> |
| 297 | + constexpr explicit optional(in_place_t, Arg&& arg); |
| 298 | + <ins>template<class F, class... Args> |
| 299 | + constexpr explicit optional(from_continuation_t, F&& f, Args&&... args);</ins> |
| 300 | + template<class U> |
| 301 | + constexpr explicit(<i>see below</i>) optional(U&& u) noexcept(<i>see below</i>); |
| 302 | + […] |
| 303 | + }; |
| 304 | + […] |
| 305 | +} |
| 306 | +</pre> |
| 307 | +</blockquote> |
| 308 | +</li> |
| 309 | + |
| 310 | +<li><p>Modify <sref ref="[optional.ref.ctor]"/> as indicated:</p> |
| 311 | + |
| 312 | +<blockquote> |
| 313 | +<pre> |
| 314 | +template<class U, class Arg> |
| 315 | + constexpr explicit optional(in_place_t, Arg&& arg); |
| 316 | +</pre> |
| 317 | +<blockquote> |
| 318 | +<p> |
| 319 | +-1- <i>Constraints</i>: […] |
| 320 | +<p/> |
| 321 | +-2- <i>Effects</i>: […] |
| 322 | +<p/> |
| 323 | +-3- <i>Postconditions</i>: […] |
| 324 | +</p> |
| 325 | +</blockquote> |
| 326 | +<pre> |
| 327 | +<ins>template<class F, class Arg> |
| 328 | + constexpr explicit optional(from_continuation_t, F&& f, Arg&& arg);</ins> |
| 329 | +</pre> |
| 330 | +<blockquote> |
| 331 | +<p> |
| 332 | +<ins>-?- <i>Mandates</i>: <tt>decltype(std::invoke(std::forward<F>(f), std::forward<Args>(args)...))</tt> |
| 333 | +is <tt>T&</tt>.</ins> |
| 334 | +<p/> |
| 335 | +<ins>-?- <i>Effects</i>: Equivalent to: <tt><i>convert-ref-init-val</i>(std::invoke(std::forward<F>(f), |
| 336 | +std::forward<Args>(args)...))</tt>.</ins> |
| 337 | +<p/> |
| 338 | +<ins>-?- <i>Postconditions</i>: `*this` contains a value.</ins> |
| 339 | +</p> |
| 340 | +</blockquote> |
| 341 | +</blockquote> |
| 342 | +</li> |
| 343 | + |
| 344 | +</ol> |
| 345 | + |
| 346 | + |
| 347 | +</resolution> |
| 348 | + |
| 349 | +</issue> |
0 commit comments