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 &quot;sink&quot; 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 &quot;moving by value&quot;).</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 &quot;moved-from&quot; state if a conv=
ersion in involved (e.g. <span style=3D"font-family: courier new,monospace;=
">unique_ptr&lt;derived&gt;&amp;&amp;</span> to <span style=3D"font-family:=
 courier new,monospace;">unique_ptr&lt;base&gt;&amp;&amp;</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&lt;T&gt;</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">&lt;string&gt;</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">&lt;string&gt;</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&lt;T&gt;</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&lt;T&gt;</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 =
&quot;pass by value&quot; approach. <span style=3D"font-family: courier new=
,monospace;">sink&lt;T&gt;</span> requires that <span style=3D"font-family:=
 courier new,monospace;">T</span> be <i>nothrow move constructible</i>.<br>=
</p><br>In conclusion, &quot;pass by sink&quot;:<br><ul><li>is just as simp=
le to teach as &quot;pass by value&quot; and <i>much</i> simpler to teach t=
han multiple overloads or perfect forwarding;<br></li><li>is <i>much</i> sa=
fer than &quot;pass by value&quot;, multiple overloads or perfect forwardin=
g (statically enforced correctness at compile time);<br></li><li>is probabl=
y more performant than &quot;pass by value&quot; 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 &quot;makes =
simple tasks simple&quot;.<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&quot; 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--

.