Topic: [std-proposals] std::sink<T> a better solution t
Author: joseph.thomson@gmail.com
Date: Thu, 19 Oct 2017 17:29:53 -0700 (PDT)
Raw View
------=_Part_2830_1944162304.1508459393414
Content-Type: multipart/alternative;
boundary="----=_Part_2831_1561848730.1508459393415"
------=_Part_2831_1561848730.1508459393415
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
I recently watched this talk <https://youtu.be/PNRju6_yn3o> about the issue=
=20
of function overload proliferation when supporting both move and copy=20
semantics for "sink" parameters. Long story short, the speaker recommends=
=20
passing by value because of its simplicity and good (but not perfect)=20
performance. However, there is a problem with pass by value. Consider a=20
constructor that takes two string arguments by value:
widget(string s1, string s2) : m_s1(move(s1)), m_s2(move(s2)) {}
Say we construct a widget by moving in one string and copying in another:
widget(move(m_first), m_last);
This will result in the following sequence of operations (assuming=20
left-to-right evaluation):
1. s1 is move constructed from m_first
2. s2 is copy constructed from m_last
3. m_s1 is move constructed from s1
4. m_s2 is move constructed from s2
=20
If the copy constructor in step 2 throws, s1 will be destructed and m_first=
=20
will be left empty, its original value irretrievably lost. This means that=
=20
the user of widget cannot provide the strong exception safety guarantee=20
without making a copy of m_first. This is bad because:
- It forces us to make potentially expensive copies if we want exception=
=20
safe code.
- It makes APIs hard to use correctly (requires knowledge of the dangers=
=20
of "moving by value").
- The problem is unavoidable if some arguments are move-only.
=20
This problem still exists if we write all of the overloads manually,=20
because moved arguments may still end up in a "moved-from" state if a=20
conversion in involved (e.g. unique_ptr<derived>&& to unique_ptr<base>&&),=
=20
and perfect forwarding will only work with metaprogramming magic to ensure=
=20
all throwing operations happen first (and even then, arguments should not=
=20
be moved if they might throw; see below). Enter sink<T>:
widget(sink<string> s1, sink<string> s2) : m_s1(move(s1)), m_s2(move(s2)) {=
}
If we have the same calling code as before...
widget(move(m_first), m_last);
....we now get the following sequence of operations:
1. s1 stores a *reference* to m_first
2. s2 stores a *copy* of m_last
3. m_s1 is move constructed from the reference within s1
4. m_s2 is move constructed from the copy within s2
=20
Now if the copy constructor in step 2 throws, the value of m_first is left=
=20
in tact because s1 only contains a reference to it. In general, sink<T>=20
behaves like so:
- It stores a reference to arguments that are *nothrow copy or move=20
convertible* to T.
- It stores a copy (of type T) of arguments that are *copy convertible*=
=20
to T but *not *nothrow copy convertible to T.
- It does not accept arguments that are *only move convertible* to T but=
*not=20
*nothrow move convertible to T.
sink<T> is then move convertible to T, which involves copying or moving=20
stored references out as appropriate, and moving out stored values. Thanks=
=20
to RVO, this means that we save an additional move operation compared to=20
the "pass by value" approach. sink<T> requires that T be *nothrow move=20
constructible*.
In conclusion, "pass by sink":
- is just as simple to teach as "pass by value" and *much* simpler to=20
teach than multiple overloads or perfect forwarding;
- is *much* safer than "pass by value", multiple overloads or perfect=20
forwarding (statically enforced correctness at compile time);
- is probably more performant than "pass by value" and almost as=20
performant as perfect forwarding (extracting the value will require a=20
function pointer indirection with an efficient implementation); and
- it Just Works=E2=84=A2, or to quote Bjarne Stroustrup, it "makes simpl=
e tasks=20
simple".
=20
Check out this working demo=20
<http://coliru.stacked-crooked.com/a/c8bd6af7a8161281> and let me know what=
=20
you think of the idea (the inefficient implementation is for demo purposes=
=20
only).
--=20
You received this message because you are subscribed to the Google Groups "=
ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp=
..org/d/msgid/std-proposals/deaca0b0-1db5-418a-a981-57f3b222eae4%40isocpp.or=
g.
------=_Part_2831_1561848730.1508459393415
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">I recently watched <a href=3D"https://youtu.be/PNRju6_yn3o=
">this talk</a> about the issue of function overload proliferation when sup=
porting both move and copy semantics for "sink" parameters. Long =
story short, the speaker recommends passing by value because of its simplic=
ity and good (but not perfect) performance. However, there is a problem wit=
h pass by value. Consider a constructor that takes two <span style=3D"font-=
family: courier new,monospace;">string</span> arguments by value:<br><p><br=
></p><p></p><div style=3D"background-color: rgb(250, 250, 250); border-colo=
r: rgb(187, 187, 187); border-style: solid; border-width: 1px; overflow-wra=
p: break-word;" class=3D"prettyprint"><code class=3D"prettyprint"><div clas=
s=3D"subprettyprint"><span style=3D"color: #000;" class=3D"styled-by-pretti=
fy">widget</span><span style=3D"color: #660;" class=3D"styled-by-prettify">=
(</span><span style=3D"color: #008;" class=3D"styled-by-prettify">string</s=
pan><span style=3D"color: #000;" class=3D"styled-by-prettify"> s1</span><sp=
an style=3D"color: #660;" class=3D"styled-by-prettify">,</span><span style=
=3D"color: #000;" class=3D"styled-by-prettify"> </span><span style=3D"color=
: #008;" class=3D"styled-by-prettify">string</span><span style=3D"color: #0=
00;" class=3D"styled-by-prettify"> s2</span><span style=3D"color: #660;" cl=
ass=3D"styled-by-prettify">)</span><span style=3D"color: #000;" class=3D"st=
yled-by-prettify"> </span><span style=3D"color: #660;" class=3D"styled-by-p=
rettify">:</span><span style=3D"color: #000;" class=3D"styled-by-prettify">=
m_s1</span><span style=3D"color: #660;" class=3D"styled-by-prettify">(</sp=
an><span style=3D"color: #000;" class=3D"styled-by-prettify">move</span><sp=
an style=3D"color: #660;" class=3D"styled-by-prettify">(</span><span style=
=3D"color: #000;" class=3D"styled-by-prettify">s1</span><span style=3D"colo=
r: #660;" class=3D"styled-by-prettify">)),</span><span style=3D"color: #000=
;" class=3D"styled-by-prettify"> m_s2</span><span style=3D"color: #660;" cl=
ass=3D"styled-by-prettify">(</span><span style=3D"color: #000;" class=3D"st=
yled-by-prettify">move</span><span style=3D"color: #660;" class=3D"styled-b=
y-prettify">(</span><span style=3D"color: #000;" class=3D"styled-by-prettif=
y">s2</span><span style=3D"color: #660;" class=3D"styled-by-prettify">))</s=
pan><span style=3D"color: #000;" class=3D"styled-by-prettify"> </span><span=
style=3D"color: #660;" class=3D"styled-by-prettify">{}</span></div></code>=
</div><br><p></p><p>Say we construct a <span style=3D"font-family: courier =
new,monospace;">widget</span> by moving in one <span style=3D"font-family: =
courier new,monospace;">string</span> and copying in another:<br></p><p><br=
></p><p></p><div style=3D"background-color: rgb(250, 250, 250); border-colo=
r: rgb(187, 187, 187); border-style: solid; border-width: 1px; overflow-wra=
p: break-word;" class=3D"prettyprint"><code class=3D"prettyprint"><div clas=
s=3D"subprettyprint"><span style=3D"color: #000;" class=3D"styled-by-pretti=
fy">widget</span><span style=3D"color: #660;" class=3D"styled-by-prettify">=
(</span><span style=3D"color: #000;" class=3D"styled-by-prettify">move</spa=
n><span style=3D"color: #660;" class=3D"styled-by-prettify">(</span><span s=
tyle=3D"color: #000;" class=3D"styled-by-prettify">m_first</span><span styl=
e=3D"color: #660;" class=3D"styled-by-prettify">),</span><span style=3D"col=
or: #000;" class=3D"styled-by-prettify"> m_last</span><span style=3D"color:=
#660;" class=3D"styled-by-prettify">);</span></div></code></div><br><p></p=
><p>This will result in the following sequence of operations (assuming left=
-to-right evaluation):</p><ol><li><span style=3D"font-family: courier new,m=
onospace;">s1</span> is move constructed from <span style=3D"font-family: c=
ourier new,monospace;">m_first</span><br></li><li><span style=3D"font-famil=
y: courier new,monospace;">s2</span> is copy constructed from <span style=
=3D"font-family: courier new,monospace;">m_last</span><br></li><li><span st=
yle=3D"font-family: courier new,monospace;">m_s1</span> is move constructed=
from <span style=3D"font-family: courier new,monospace;">s1</span><br></li=
><li><span style=3D"font-family: courier new,monospace;">m_s2</span> is mov=
e constructed from <span style=3D"font-family: courier new,monospace;">s2</=
span><br></li></ol><p>If the copy constructor in step 2 throws, <span style=
=3D"font-family: courier new,monospace;">s1</span> will be destructed and <=
span style=3D"font-family: courier new,monospace;">m_first</span> will be l=
eft empty, its original value irretrievably lost. This means that the user =
of <span style=3D"font-family: courier new,monospace;">widget</span> cannot=
provide the strong exception safety guarantee without making a copy of <sp=
an style=3D"font-family: courier new,monospace;">m_first</span>. This is ba=
d because:</p><ul><li>It forces us to make potentially expensive copies if =
we want exception safe code.</li><li>It makes APIs hard to use correctly (r=
equires knowledge of the dangers of "moving by value").</li><li>T=
he problem is unavoidable if some arguments are move-only.<br></li></ul>Thi=
s problem still exists if we write all of the overloads manually, because m=
oved arguments may still end up in a "moved-from" state if a conv=
ersion in involved (e.g. <span style=3D"font-family: courier new,monospace;=
">unique_ptr<derived>&&</span> to <span style=3D"font-family:=
courier new,monospace;">unique_ptr<base>&&</span>), and perf=
ect forwarding will only work with metaprogramming magic to ensure all thro=
wing operations happen first (and even then, arguments should not be moved =
if they might throw; see below). Enter <span style=3D"font-family: courier =
new,monospace;">sink<T></span>:<br><br><p></p><div style=3D"backgroun=
d-color: rgb(250, 250, 250); border-color: rgb(187, 187, 187); border-style=
: solid; border-width: 1px; overflow-wrap: break-word;" class=3D"prettyprin=
t"><code class=3D"prettyprint"><div class=3D"subprettyprint"><span style=3D=
"color: #000;" class=3D"styled-by-prettify">widget</span><span style=3D"col=
or: #660;" class=3D"styled-by-prettify">(</span><span style=3D"color: #000;=
" class=3D"styled-by-prettify">sink</span><span style=3D"color: #080;" clas=
s=3D"styled-by-prettify"><string></span><span style=3D"color: #000;" =
class=3D"styled-by-prettify"> s1</span><span style=3D"color: #660;" class=
=3D"styled-by-prettify">,</span><span style=3D"color: #000;" class=3D"style=
d-by-prettify"> sink</span><span style=3D"color: #080;" class=3D"styled-by-=
prettify"><string></span><span style=3D"color: #000;" class=3D"styled=
-by-prettify"> s2</span><span style=3D"color: #660;" class=3D"styled-by-pre=
ttify">)</span><span style=3D"color: #000;" class=3D"styled-by-prettify"> <=
/span><span style=3D"color: #660;" class=3D"styled-by-prettify">:</span><sp=
an style=3D"color: #000;" class=3D"styled-by-prettify"> m_s1</span><span st=
yle=3D"color: #660;" class=3D"styled-by-prettify">(</span><span style=3D"co=
lor: #000;" class=3D"styled-by-prettify">move</span><span style=3D"color: #=
660;" class=3D"styled-by-prettify">(</span><span style=3D"color: #000;" cla=
ss=3D"styled-by-prettify">s1</span><span style=3D"color: #660;" class=3D"st=
yled-by-prettify">)),</span><span style=3D"color: #000;" class=3D"styled-by=
-prettify"> m_s2</span><span style=3D"color: #660;" class=3D"styled-by-pret=
tify">(</span><span style=3D"color: #000;" class=3D"styled-by-prettify">mov=
e</span><span style=3D"color: #660;" class=3D"styled-by-prettify">(</span><=
span style=3D"color: #000;" class=3D"styled-by-prettify">s2</span><span sty=
le=3D"color: #660;" class=3D"styled-by-prettify">))</span><span style=3D"co=
lor: #000;" class=3D"styled-by-prettify"> </span><span style=3D"color: #660=
;" class=3D"styled-by-prettify">{}</span></div></code></div><br><p></p><p>I=
f we have the same calling code as before...</p><p><br></p><p></p><div styl=
e=3D"background-color: rgb(250, 250, 250); border-color: rgb(187, 187, 187)=
; border-style: solid; border-width: 1px; overflow-wrap: break-word;" class=
=3D"prettyprint"><code class=3D"prettyprint"><div class=3D"subprettyprint">=
<span style=3D"color: #000;" class=3D"styled-by-prettify">widget</span><spa=
n style=3D"color: #660;" class=3D"styled-by-prettify">(</span><span style=
=3D"color: #000;" class=3D"styled-by-prettify">move</span><span style=3D"co=
lor: #660;" class=3D"styled-by-prettify">(</span><span style=3D"color: #000=
;" class=3D"styled-by-prettify">m_first</span><span style=3D"color: #660;" =
class=3D"styled-by-prettify">),</span><span style=3D"color: #000;" class=3D=
"styled-by-prettify"> m_last</span><span style=3D"color: #660;" class=3D"st=
yled-by-prettify">);</span></div></code></div><br><p></p><p>...we now get t=
he following sequence of operations:</p><ol><li><span style=3D"font-family:=
courier new,monospace;">s1</span> stores a <i>reference</i> to <span style=
=3D"font-family: courier new,monospace;">m_first</span><br></li><li><span s=
tyle=3D"font-family: courier new,monospace;">s2</span> stores a <i>copy</i>=
of <span style=3D"font-family: courier new,monospace;">m_last</span><br></=
li><li><span style=3D"font-family: courier new,monospace;">m_s1</span> is m=
ove constructed from the reference within <span style=3D"font-family: couri=
er new,monospace;">s1</span><br></li><li><span style=3D"font-family: courie=
r new,monospace;">m_s2</span> is move constructed from the copy within <spa=
n style=3D"font-family: courier new,monospace;">s2</span><br></li></ol><p>N=
ow if the copy constructor in step 2 throws, the value of <span style=3D"fo=
nt-family: courier new,monospace;">m_first</span> is left in tact because <=
span style=3D"font-family: courier new,monospace;">s1</span> only contains =
a reference to it. In general, <span style=3D"font-family: courier new,mono=
space;">sink<T></span> behaves like so:</p><ul><li>It stores a refere=
nce to arguments that are <i>nothrow copy or move convertible</i> to <span =
style=3D"font-family: courier new,monospace;">T</span>.</li><li>It stores a=
copy (of type <span style=3D"font-family: courier new,monospace;">T</span>=
) of arguments that are <i>copy convertible</i> to <span style=3D"font-fami=
ly: courier new,monospace;">T</span> but <i>not </i>nothrow copy convertibl=
e to <span style=3D"font-family: courier new,monospace;">T</span>.<br></li>=
<li>It does not accept arguments that are <i>only move convertible</i> to <=
span style=3D"font-family: courier new,monospace;">T</span> but <i>not </i>=
nothrow move convertible to <span style=3D"font-family: courier new,monospa=
ce;">T</span>.</li></ul><p><span style=3D"font-family: courier new,monospac=
e;">sink<T></span> is then move convertible to <span style=3D"font-fa=
mily: courier new,monospace;">T</span>, which involves copying or moving st=
ored references out as appropriate, and moving out stored values. Thanks to=
RVO, this means that we save an additional move operation compared to the =
"pass by value" approach. <span style=3D"font-family: courier new=
,monospace;">sink<T></span> requires that <span style=3D"font-family:=
courier new,monospace;">T</span> be <i>nothrow move constructible</i>.<br>=
</p><br>In conclusion, "pass by sink":<br><ul><li>is just as simp=
le to teach as "pass by value" and <i>much</i> simpler to teach t=
han multiple overloads or perfect forwarding;<br></li><li>is <i>much</i> sa=
fer than "pass by value", multiple overloads or perfect forwardin=
g (statically enforced correctness at compile time);<br></li><li>is probabl=
y more performant than "pass by value" and almost as performant a=
s perfect forwarding (extracting the value will require a function pointer =
indirection with an efficient implementation); and</li><li>it Just Works<sp=
an class=3D"_Tgc">=E2=84=A2, or to quote Bjarne Stroustrup, it "makes =
simple tasks simple".<br></span></li></ul><p>Check out <a href=3D"http=
://coliru.stacked-crooked.com/a/c8bd6af7a8161281">this working demo</a> and=
let me know what you think of the idea (the inefficient implementation is =
for demo purposes only).</p></div>
<p></p>
-- <br />
You received this message because you are subscribed to the Google Groups &=
quot;ISO C++ Standard - Future Proposals" group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href=3D"mailto:std-proposals+unsubscribe@isocpp.org">std-proposa=
ls+unsubscribe@isocpp.org</a>.<br />
To post to this group, send email to <a href=3D"mailto:std-proposals@isocpp=
..org">std-proposals@isocpp.org</a>.<br />
To view this discussion on the web visit <a href=3D"https://groups.google.c=
om/a/isocpp.org/d/msgid/std-proposals/deaca0b0-1db5-418a-a981-57f3b222eae4%=
40isocpp.org?utm_medium=3Demail&utm_source=3Dfooter">https://groups.google.=
com/a/isocpp.org/d/msgid/std-proposals/deaca0b0-1db5-418a-a981-57f3b222eae4=
%40isocpp.org</a>.<br />
------=_Part_2831_1561848730.1508459393415--
------=_Part_2830_1944162304.1508459393414--
.