forked from lwg/issues
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New issue from Jiang An: "Monadic operations should be ADL-proof"
- Loading branch information
Showing
1 changed file
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,323 @@ | ||
<?xml version='1.0' encoding='utf-8' standalone='no'?> | ||
<!DOCTYPE issue SYSTEM "lwg-issue.dtd"> | ||
|
||
<issue num="3973" status="New"> | ||
<title>Monadic operations should be ADL-proof</title> | ||
<section><sref ref="[expected.object.monadic]"/><sref ref="[optional.monadic]"/></section> | ||
<submitter>Jiang An</submitter> | ||
<date>10 Aug 2023</date> | ||
<priority>99</priority> | ||
|
||
<discussion> | ||
<p> | ||
LWG <iref ref="3938"/> switched to use <tt>**this</tt> to access the value stored in <tt>std::expected</tt>. | ||
However, as shown in LWG <iref ref="3969"/>, <tt>**this</tt> can trigger ADL and find an unwanted overload, | ||
and thus may caused unintended behavior. | ||
<p/> | ||
Current implementations behave correctly (<a href="https://godbolt.org/z/3b1Tbs1jd">Godbolt link</a>): they | ||
don't direct use <tt>**this</tt>, but use the name of the union member instead. | ||
<p/> | ||
Moreover, <paper num="P2407R5"/> will change the monadic operations of <tt>std::optional</tt> to use <tt>**this</tt>, | ||
which is also problematic. | ||
</p> | ||
</discussion> | ||
|
||
<resolution> | ||
<p> | ||
This wording is relative to <paper num="N4958"/>. | ||
</p> | ||
|
||
<ol> | ||
|
||
<li><p>Modify the <sref ref="[expected.object.monadic]"/> as indicated:</p> | ||
|
||
<blockquote class="note"> | ||
<p> | ||
[<i>Drafting note:</i> Effectively replace all occurrences of <tt>**this</tt> by <tt>operator*()</tt>.] | ||
</p> | ||
</blockquote> | ||
|
||
<blockquote> | ||
<pre> | ||
template<class F> constexpr auto and_then(F&& f) &; | ||
template<class F> constexpr auto and_then(F&& f) const &; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-1- Let <tt>U</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(<del>**this</del><ins>operator*()</ins>)>></tt>. | ||
<p/> | ||
-2- […] | ||
<p/> | ||
-3- […] | ||
<p/> | ||
-4- <i>Effects</i>: Equivalent to: | ||
</p> | ||
<blockquote><pre> | ||
if (has_value()) | ||
return invoke(std::forward<F>(f), <del>**this</del><ins>operator*()</ins>); | ||
else | ||
return U(unexpect, error()); | ||
</pre></blockquote> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto and_then(F&& f) &&; | ||
template<class F> constexpr auto and_then(F&& f) const &&; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-5- Let <tt>U</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype((std::move(<del>**this</del><ins>operator*()</ins>))>></tt>. | ||
<p/> | ||
-6- […] | ||
<p/> | ||
-7- […] | ||
<p/> | ||
-8- <i>Effects</i>: Equivalent to: | ||
</p> | ||
<blockquote><pre> | ||
if (has_value()) | ||
return invoke(std::forward<F>(f), std::move(<del>**this</del><ins>operator*()</ins>)); | ||
else | ||
return U(unexpect, std::move(error())); | ||
</pre></blockquote> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto or_else(F&& f) &; | ||
template<class F> constexpr auto or_else(F&& f) const &; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-9- Let <tt>G</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(error())>></tt>. | ||
<p/> | ||
-10- <i>Constraints</i>: <tt>is_constructible_v<T, decltype(<del>**this</del><ins>operator*()</ins>)></tt> is <tt>true</tt>. | ||
<p/> | ||
-11- […] | ||
<p/> | ||
-12- <i>Effects</i>: Equivalent to: | ||
</p> | ||
<blockquote><pre> | ||
if (has_value()) | ||
return G(in_place, <del>**this</del><ins>operator*()</ins>); | ||
else | ||
return invoke(std::forward<F>(f), error()); | ||
</pre></blockquote> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto or_else(F&& f) &&; | ||
template<class F> constexpr auto or_else(F&& f) const &&; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-13- Let <tt>G</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>></tt>. | ||
<p/> | ||
-14- <i>Constraints</i>: <tt>is_constructible_v<T, decltype(std::move(<del>**this</del><ins>operator*()</ins>))></tt> is <tt>true</tt>. | ||
<p/> | ||
-15- […] | ||
<p/> | ||
-16- <i>Effects</i>: Equivalent to: | ||
</p> | ||
<blockquote><pre> | ||
if (has_value()) | ||
return G(in_place, std::move(<del>**this</del><ins>operator*()</ins>)); | ||
else | ||
return invoke(std::forward<F>(f), std::move(error())); | ||
</pre></blockquote> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto transform(F&& f) &; | ||
template<class F> constexpr auto transform(F&& f) const &; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-17- Let <tt>U</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(<del>**this</del><ins>operator*()</ins>)>></tt>. | ||
<p/> | ||
-18- […] | ||
<p/> | ||
-19- <i>Mandates</i>: <tt>U</tt> is a valid value type for <tt>expected</tt>. If <tt>is_void_v<U></tt> is <tt>false</tt>, | ||
the declaration | ||
</p> | ||
<blockquote><pre> | ||
U u(invoke(std::forward<F>(f), <del>**this</del><ins>operator*()</ins>)); | ||
</pre></blockquote> | ||
<p> | ||
is well-formed. | ||
<p/> | ||
-20- <i>Effects</i>: | ||
</p> | ||
<ol style="list-style-type: none"> | ||
<li><p>(20.1) — […]</p></li> | ||
<li><p>(20.2) — Otherwise, if <tt>is_void_v<U></tt> is <tt>false</tt>, returns an <tt>expected<U, E></tt> | ||
object whose <tt><i>has_val</i></tt> member is <tt>true</tt> and <tt><i>val</i></tt> member is direct-non-list-initialized | ||
with <tt>invoke(std::forward<F>(f), <del>**this</del><ins>operator*()</ins>)</tt>.</p></li> | ||
<li><p>(20.3) — Otherwise, evaluates <tt>invoke(std::forward<F>(f), <del>**this</del><ins>operator*()</ins>)</tt> | ||
and then returns <tt>expected<U, E>()</tt>.</p></li> | ||
</ol> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto transform(F&& f) &&; | ||
template<class F> constexpr auto transform(F&& f) const &&; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-21- Let <tt>U</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(std::move(<del>**this</del><ins>operator*()</ins>))>></tt>. | ||
<p/> | ||
-22- […] | ||
<p/> | ||
-23- <i>Mandates</i>: <tt>U</tt> is a valid value type for <tt>expected</tt>. If <tt>is_void_v<U></tt> is <tt>false</tt>, | ||
the declaration | ||
</p> | ||
<blockquote><pre> | ||
U u(invoke(std::forward<F>(f), std::move(<del>**this</del><ins>operator*()</ins>))); | ||
</pre></blockquote> | ||
<p> | ||
is well-formed. | ||
<p/> | ||
-24- <i>Effects</i>: | ||
</p> | ||
<ol style="list-style-type: none"> | ||
<li><p>(24.1) — […]</p></li> | ||
<li><p>(24.2) — Otherwise, if <tt>is_void_v<U></tt> is <tt>false</tt>, returns an <tt>expected<U, E></tt> | ||
object whose <tt><i>has_val</i></tt> member is <tt>true</tt> and <tt><i>val</i></tt> member is direct-non-list-initialized | ||
with <tt>invoke(std::forward<F>(f), std::move(<del>**this</del><ins>operator*()</ins>))</tt>.</p></li> | ||
<li><p>(24.3) — Otherwise, evaluates <tt>invoke(std::forward<F>(f), std::move(<del>**this</del><ins>operator*()</ins>))</tt> | ||
and then returns <tt>expected<U, E>()</tt>.</p></li> | ||
</ol> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto transform_error(F&& f) &; | ||
template<class F> constexpr auto transform_error(F&& f) const &; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-25- Let <tt>G</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(error())>></tt>. | ||
<p/> | ||
-26- <i>Constraints</i>: <tt>is_constructible_v<T, decltype(<del>**this</del><ins>operator*()</ins>)></tt> is <tt>true</tt>. | ||
<p/> | ||
-27- <i>Mandates</i>: […] | ||
<p/> | ||
-28- <i>Returns</i>: If <tt>has_value()</tt> is <tt>true</tt>, <tt>expected<T, G>(in_place, <del>**this</del><ins>operator*()</ins>)</tt>; | ||
otherwise, an <tt>expected<T, G></tt> object whose <tt><i>has_val</i></tt> member is <tt>false</tt> and | ||
<tt><i>unex</i></tt> member is direct-non-list-initialized with <tt>invoke(std::forward<F>(f), error())</tt>. | ||
</p> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto transform_error(F&& f) &&; | ||
template<class F> constexpr auto transform_error(F&& f) const &&; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-29- Let <tt>G</tt> be <tt>remove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>></tt>. | ||
<p/> | ||
-30- <i>Constraints</i>: <tt>is_constructible_v<T, decltype(std::move(<del>**this</del><ins>operator*()</ins>))></tt> is <tt>true</tt>. | ||
<p/> | ||
-31- <i>Mandates</i>: […] | ||
<p/> | ||
-32- <i>Returns</i>: If <tt>has_value()</tt> is <tt>true</tt>, <tt>expected<T, G>(in_place, std::move(<del>**this</del><ins>operator*()</ins>))</tt>; | ||
otherwise, an <tt>expected<T, G></tt> object whose <tt><i>has_val</i></tt> member is <tt>false</tt> and | ||
<tt><i>unex</i></tt> member is direct-non-list-initialized with <tt>invoke(std::forward<F>(f), std::move(error()))</tt>. | ||
</p> | ||
</blockquote> | ||
</blockquote> | ||
</li> | ||
|
||
<li><p>Modify the <sref ref="[optional.monadic]"/> as indicated:</p> | ||
|
||
|
||
<blockquote class="note"> | ||
<p> | ||
[<i>Drafting note:</i> Effectively replace all occurrences of <tt>value()</tt> by <tt>operator*()</tt>.] | ||
</p> | ||
</blockquote> | ||
|
||
<blockquote> | ||
<pre> | ||
template<class F> constexpr auto and_then(F&& f) &; | ||
template<class F> constexpr auto and_then(F&& f) const &; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-1- Let <tt>U</tt> be <tt>invoke_result_t<F, decltype(<del>value()</del><ins>operator*()</ins>)></tt>. | ||
<p/> | ||
-2- […] | ||
<p/> | ||
-3- <i>Effects</i>: Equivalent to: | ||
</p> | ||
<blockquote><pre> | ||
if (*this) { | ||
return invoke(std::forward<F>(f), <del>value()</del><ins>operator*()</ins>); | ||
} else { | ||
return remove_cvref_t<U>(); | ||
} | ||
</pre></blockquote> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto and_then(F&& f) &&; | ||
template<class F> constexpr auto and_then(F&& f) const &&; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-4- Let <tt>U</tt> be <tt>invoke_result_t<F, decltype(std::move(<del>value()</del><ins>operator*()</ins>))></tt>. | ||
<p/> | ||
-5- […] | ||
<p/> | ||
-6- <i>Effects</i>: Equivalent to: | ||
</p> | ||
<blockquote><pre> | ||
if (*this) { | ||
return invoke(std::forward<F>(f), std::move(<del>value()</del><ins>operator*()</ins>)); | ||
} else { | ||
return remove_cvref_t<U>(); | ||
} | ||
</pre></blockquote> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto transform(F&& f) &; | ||
template<class F> constexpr auto transform(F&& f) const &; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-7- Let <tt>U</tt> be <tt>remove_cv_t<invoke_result_t<F, decltype(<del>value()</del><ins>operator*()</ins>)>></tt>. | ||
<p/> | ||
-8- <i>Mandates</i>: <tt>U</tt> is a non-array object type other than <tt>in_place_t</tt> or <tt>nullopt_t</tt>. The declaration | ||
</p> | ||
<blockquote><pre> | ||
U u(invoke(std::forward<F>(f), <del>value()</del><ins>operator*()</ins>)); | ||
</pre></blockquote> | ||
<p> | ||
is well-formed for some invented variable <tt>u</tt>. | ||
<p/> | ||
[…] | ||
<p/> | ||
-9- <i>Returns</i>: If <tt>*this</tt> contains a value, an <tt>optional<U></tt> object whose contained value is | ||
direct-non-list-initialized with <tt>invoke(std::forward<F>(f), <del>value()</del><ins>operator*()</ins>)</tt>; otherwise, | ||
<tt>optional<U>()</tt>. | ||
</p> | ||
</blockquote> | ||
<pre> | ||
template<class F> constexpr auto transform(F&& f) &&; | ||
template<class F> constexpr auto transform(F&& f) const &&; | ||
</pre> | ||
<blockquote> | ||
<p> | ||
-10- Let <tt>U</tt> be <tt>remove_cv_t<invoke_result_t<F, decltype(std::move(<del>value()</del><ins>operator*()</ins>))>></tt>. | ||
<p/> | ||
-11- <i>Mandates</i>: <tt>U</tt> is a non-array object type other than <tt>in_place_t</tt> or <tt>nullopt_t</tt>. The declaration | ||
</p> | ||
<blockquote><pre> | ||
U u(invoke(std::forward<F>(f), std::move(<del>value()</del><ins>operator*()</ins>))); | ||
</pre></blockquote> | ||
<p> | ||
is well-formed for some invented variable <tt>u</tt>. | ||
<p/> | ||
[…] | ||
<p/> | ||
-12- <i>Returns</i>: If <tt>*this</tt> contains a value, an <tt>optional<U></tt> object whose contained value is | ||
direct-non-list-initialized with <tt>invoke(std::forward<F>(f), std::move(<del>value()</del><ins>operator*()</ins>))</tt>; otherwise, | ||
<tt>optional<U>()</tt>. | ||
</p> | ||
</blockquote> | ||
</blockquote> | ||
</li> | ||
</ol> | ||
</resolution> | ||
|
||
</issue> |