Skip to content

Commit e6c575c

Browse files
committed
New issue from Rasheeq Azad: "std::optional<T>::transform cannot be implemented while supporting program-defined specializations"
1 parent 412d674 commit e6c575c

File tree

1 file changed

+349
-0
lines changed

1 file changed

+349
-0
lines changed

xml/issue4509.xml

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
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&lt;T&gt;::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&lt;T&gt;::transform(F&amp;&amp;f)&amp;</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&lt;invoke_result_t&lt;F, decltype((<i>val</i>))&gt;&gt;</tt>.
20+
<p/>
21+
<i>Mandates</i>: [&hellip;]
22+
<p/>
23+
[<i>Note 1</i>: There is no requirement that `U` is movable (<sref ref="[dcl.init.general]"/>).
24+
&mdash; <i>end note</i>]
25+
<p/>
26+
<i>Returns</i>: If `*this` contains a value, an <tt>optional&lt;U&gt;</tt> object whose
27+
contained value is direct-non-list-initialized with <tt>invoke(std::forward&lt;F&gt;(f), <i>val</i>)</tt>;
28+
otherwise, <tt>optional&lt;U&gt;()</tt>.
29+
</p>
30+
</blockquote>
31+
<p>
32+
However, none of the standard constructors or other member functions of
33+
<tt>optional&lt;U&gt;</tt> provide a surefire way to initialize the contained `U` value with
34+
an expression like <tt>invoke(std::forward&lt;F&gt;(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&lt;class _F&gt; struct __later {
41+
_F __f;
42+
operator decltype(std::move(__f)())() &amp;&amp; { return std::move(__f)(); }
43+
};
44+
45+
template&lt;class _T&gt; class optional {
46+
// etc.
47+
public:
48+
template&lt;class _F&gt; constexpr auto transform(_F &amp;&amp;__f) &amp; {
49+
using _U = remove_cv_t&lt;invoke_result_t&lt;_F, _T&amp;&gt;&gt;;
50+
if(!has_value()) return optional&lt;_U&gt;();
51+
return optional&lt;_U&gt;(in_place, __later([&amp;] -&gt; _U {
52+
return std::invoke(std::forward&lt;_F&gt;(__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&amp;&amp;)</tt> constructor:
61+
</p>
62+
<blockquote><pre>
63+
struct oops {
64+
oops() = default;
65+
oops(auto&amp;&amp;) { std::cout &lt;&lt; "launching missiles\n"; }
66+
};
67+
68+
int main() {
69+
std::optional&lt;int&gt; oi(5);
70+
oi.transform([](auto&amp; 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&lt;T&gt;::transform</tt> with a non-standard constructor on their
81+
<tt>std::optional&lt;T&gt;</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&lt;typename _T&gt;
90+
class optional {
91+
// etc.
92+
public:
93+
template&lt;typename _F, typename _V&gt;
94+
constexpr optional(__optional_from_invocable_tag, _F &amp;&amp;__f, _V &amp;&amp;__v)
95+
: __present(true)
96+
, __val(std::invoke(std::forward&lt;_F&gt;(__f), std::forward&lt;_V&gt;(__v)))
97+
{ }
98+
99+
template&lt;class _F&gt; constexpr auto transform(_F &amp;&amp;__f) &amp; {
100+
using _U = remove_cv_t&lt;invoke_result_t&lt;_F, _T&amp;&gt;&gt;;
101+
if(!has_value()) return optional&lt;_U&gt;();
102+
return optional&lt;_U&gt;(
103+
__optional_from_invocable_tag(),
104+
std::forward&lt;_F&gt;(__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&lt;std::monostate&gt;(std::in_place).transform(/* ... */)</tt>.)
120+
<p/>
121+
The root problem is that the standard interface of <tt>std::optional&lt;U&gt;</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&lt;T&gt;::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&lt;T&gt;::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&lt;T&gt;</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&lt;T&gt; {
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&lt;T&gt;</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&lt;T&amp;&gt;</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&lt;T&gt;</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` [&hellip;]. 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&lt;T&gt;</tt> constructor taking an invocable
192+
</p>
193+
194+
<ol>
195+
<li><p>Modify <sref ref="[utility.syn]"/>, header <tt>&lt;utility&gt;</tt> synopsis, as indicated:</p>
196+
197+
<blockquote>
198+
<pre>
199+
[&hellip;]
200+
namespace std {
201+
[&hellip;]
202+
template&lt;size_t I&gt;
203+
struct in_place_index_t {
204+
explicit in_place_index_t() = default;
205+
};
206+
template&lt;size_t I&gt; constexpr in_place_index_t&lt;I&gt; 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+
[&hellip;]
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&lt;class T&gt;
226+
class optional {
227+
public:
228+
[&hellip;]
229+
<i>// <sref ref="[optional.ctor]"/>, constructors</i>
230+
constexpr optional() noexcept;
231+
constexpr optional(nullopt_t) noexcept;
232+
[&hellip;]
233+
template&lt;class... Args&gt;
234+
constexpr explicit optional(in_place_t, Args&amp;&amp;...);
235+
template&lt;class U, class... Args&gt;
236+
constexpr explicit optional(in_place_t, initializer_list&lt;U&gt;, Args&amp;&amp;...);
237+
<ins>template&lt;class F, class... Args&gt;
238+
constexpr explicit optional(from_continuation_t, F&amp;&amp;, Args&amp;&amp;...);</ins>
239+
template&lt;class U = remove_cv_t&lt;T&gt;&gt;
240+
constexpr explicit(<i>see below</i>) optional(U&amp;&amp;);
241+
[&hellip;]
242+
};
243+
[&hellip;]
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&lt;class U, class... Args&gt;
254+
constexpr explicit optional(in_place_t, initializer_list&lt;U&gt; il, Args&amp;&amp;... args);
255+
</pre>
256+
<blockquote>
257+
<p>
258+
-18- <i>Constraints</i>: [&hellip;]
259+
<p/>
260+
[&hellip;]
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&lt;class F, class... Args&gt;
268+
constexpr explicit optional(from_continuation_t, F&amp;&amp; f, Args&amp;&amp;... args);</ins>
269+
</pre>
270+
<blockquote>
271+
<p>
272+
<ins>-?- <i>Mandates</i>: <tt>decltype(std::invoke(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(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&lt;F&gt;(f), std::forward&lt;Args&gt;(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&lt;class T&gt;
289+
class optional&lt;T&amp;&gt; {
290+
public:
291+
[&hellip;]
292+
<i>// <sref ref="[optional.ref.ctor]"/>, constructors</i>
293+
constexpr optional() noexcept = default;
294+
constexpr optional(nullopt_t) noexcept : optional() {}
295+
[&hellip;]
296+
template&lt;class Arg&gt;
297+
constexpr explicit optional(in_place_t, Arg&amp;&amp; arg);
298+
<ins>template&lt;class F, class... Args&gt;
299+
constexpr explicit optional(from_continuation_t, F&amp;&amp; f, Args&amp;&amp;... args);</ins>
300+
template&lt;class U&gt;
301+
constexpr explicit(<i>see below</i>) optional(U&amp;&amp; u) noexcept(<i>see below</i>);
302+
[&hellip;]
303+
};
304+
[&hellip;]
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&lt;class U, class Arg&gt;
315+
constexpr explicit optional(in_place_t, Arg&amp;&amp; arg);
316+
</pre>
317+
<blockquote>
318+
<p>
319+
-1- <i>Constraints</i>: [&hellip;]
320+
<p/>
321+
-2- <i>Effects</i>: [&hellip;]
322+
<p/>
323+
-3- <i>Postconditions</i>: [&hellip;]
324+
</p>
325+
</blockquote>
326+
<pre>
327+
<ins>template&lt;class F, class Arg&gt;
328+
constexpr explicit optional(from_continuation_t, F&amp;&amp; f, Arg&amp;&amp; arg);</ins>
329+
</pre>
330+
<blockquote>
331+
<p>
332+
<ins>-?- <i>Mandates</i>: <tt>decltype(std::invoke(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...))</tt>
333+
is <tt>T&amp;</tt>.</ins>
334+
<p/>
335+
<ins>-?- <i>Effects</i>: Equivalent to: <tt><i>convert-ref-init-val</i>(std::invoke(std::forward&lt;F&gt;(f),
336+
std::forward&lt;Args&gt;(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

Comments
 (0)