Skip to content

Commit 227ae52

Browse files
committed
New issue from Daniel: "std::function's member template target() should not lead to undefined behaviour"
1 parent 574fc2b commit 227ae52

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

xml/issue2591.xml

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
2+
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">
3+
4+
<issue num="2591" status="New">
5+
<title><tt>std::function</tt>'s member template <tt>target()</tt> should not lead to undefined behaviour</title>
6+
<section><sref ref="[func.wrap.func.targ]"/></section>
7+
<submitter>Daniel Kr&uuml;gler</submitter>
8+
<date>31 Jan 2016</date>
9+
<priority>99</priority>
10+
11+
<discussion>
12+
<p>
13+
This issue is a spin-off of LWG <iref ref="2393"/>, it solely focuses on the pre-condition of <sref ref="[func.wrap.func.targ]"/> p2:
14+
</p>
15+
<blockquote class="note"><p>
16+
<i>Requires</i>: <tt>T</tt> shall be a type that is Callable (20.9.12.2) for parameter types <tt>ArgTypes</tt> and return type
17+
<tt>R</tt>.
18+
</p></blockquote>
19+
<p>
20+
Originally, the author of this issue here had assumed that simply removing the precondition as a side-step of fixing LWG
21+
<iref ref="2393"/> would be uncontroversial. Discussions on the
22+
<a href="http://accu.org/cgi-bin/wg21/message?wg=lib&amp;msg=38356">library reflector</a> indicated that this is not the case,
23+
although it seemed that there was agreement on removing the undefined behaviour edge-case.
24+
<p/>
25+
There exist basically the following positions:
26+
</p>
27+
<ol>
28+
<li><p>The constraint should be removed completely, the function is considered as having a
29+
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3279.pdf">wide contract</a>.</p>
30+
</li>
31+
<li><p>The pre-condition should be replaced by a <i>Remarks</i> element, that has the effect of making the code ill-formed,
32+
if <tt>T</tt> is a type that is not Lvalue-Callable (20.9.11.2) for parameter types <tt>ArgTypes</tt> and return type <tt>R</tt>.
33+
Technically this approach is still conforming with a wide contract function, because the definition of this contract form
34+
depends on runtime constraints.</p></li>
35+
</ol>
36+
<p>
37+
Not yet explicitly discussed, but a possible variant of bullet (2) could be:
38+
</p>
39+
<ol start="3">
40+
<li><p>The pre-condition should be replaced by a <i>Remarks</i> element, that has the effect of SFINAE-constraining
41+
this member: "This function shall not participate in overload resolution unless <tt>T</tt> is a type that is
42+
Lvalue-Callable (20.9.11.2) for parameter types <tt>ArgTypes</tt> and return type <tt>R</tt>".
43+
</p></li>
44+
</ol>
45+
<p>
46+
The following describes a list of some selected arguments that have been provided for one or the other position
47+
using corresponding list items. Unless explicitly denoted, no difference has been accounted for option (3) over
48+
option (2).
49+
</p>
50+
<ol>
51+
<li>
52+
<ol style="list-style-type:lower-alpha">
53+
<li>
54+
<p>It reflects existing implementation practice, Visual Studio 2015 SR1, gcc 6 libstdc++, and clang 3.8.0 libc++ do accept
55+
the following code:</p>
56+
<blockquote><pre>
57+
#include &lt;functional&gt;
58+
#include &lt;iostream&gt;
59+
#include &lt;typeinfo&gt;
60+
#include "boost/function.hpp"
61+
62+
void foo(int) {}
63+
64+
int main() {
65+
std::function&lt;void(int)&gt; f(foo);
66+
std::cout &lt;&lt; f.target&lt;void(*)()&gt;() &lt;&lt; std::endl;
67+
boost::function&lt;void(int)&gt; f2(foo);
68+
std::cout &lt;&lt; f2.target&lt;void(*)()&gt;() &lt;&lt; std::endl;
69+
}
70+
</pre></blockquote>
71+
<p>
72+
and consistently output the implementation-specific result for <b>two null pointer</b> values.
73+
</p>
74+
</li>
75+
<li>
76+
<p>
77+
The current
78+
<a href="http://www.boost.org/doc/libs/1_60_0/doc/html/boost/function_base.html">Boost documentation</a>
79+
does not indicate <em>any</em> precondition for calling the <tt>target</tt> function, so it is natural
80+
that programmers would expect similar specification and behaviour for the corresponding standard component.
81+
</p>
82+
</li>
83+
<li>
84+
<p>
85+
There is a consistency argument in regard to the free function template <tt>get_deleter</tt>
86+
</p>
87+
<blockquote><pre>
88+
template&lt;class D, class T&gt;
89+
D* get_deleter(const shared_ptr&lt;T&gt;&amp; p) noexcept;
90+
</pre></blockquote>
91+
<p>
92+
This function also does not impose any pre-conditions on its template argument <tt>D</tt>.
93+
</p>
94+
</li>
95+
</ol>
96+
</li>
97+
<li>
98+
<ol style="list-style-type:lower-alpha">
99+
<li>
100+
<p>
101+
Programmers have control over the type they're passing to <tt>target&lt;T&gt;()</tt>. Passing a non-callable type
102+
can't possibly retrieve a non-null target, so it seems highly likely to be programmer error. Diagnosing that at
103+
compile time seems highly preferable to allowing this to return null, always, at runtime.
104+
</p>
105+
</li>
106+
<li>
107+
<p>
108+
If <tt>T</tt> is a reference type then the return type <tt>T*</tt> is ill-formed anyway. This implies that one can't
109+
blindly call <tt>target&lt;T&gt;</tt> without knowing what <tt>T</tt> is.
110+
</p>
111+
</li>
112+
<li>
113+
<p>
114+
It has been pointed out that some real world code, boiling down to
115+
</p>
116+
<blockquote><pre>
117+
void foo() {}
118+
119+
int main() {
120+
std::function&lt;void()&gt; f = foo;
121+
if (f.target&lt;decltype(foo)&gt;()) {
122+
<i>// fast path</i>
123+
} else {
124+
<i>// slow path</i>
125+
}
126+
}
127+
</pre></blockquote>
128+
<p>
129+
had manifested as a performance issue and preparing a patch that made the library <tt>static_assert</tt> in that
130+
case solved this problem (Note that <tt>decltype(foo)</tt> evaluates to <tt>void()</tt>, but a proper argument of
131+
<tt>target()</tt> would have been the function <em>pointer</em> type <tt>void(*)()</tt>, because a function type
132+
<tt>void()</tt> is not any <i>Callable</i> type).
133+
</p>
134+
</li>
135+
</ol>
136+
</li>
137+
</ol>
138+
<p>
139+
It might be worth adding that if use case (2 c) is indeed an often occurring idiom, it would make sense to consider
140+
to provide an explicit conversion to a function pointer (w/o template parameters that could be provided incorrectly),
141+
if the <tt>std::function</tt> object at runtime conditions contains a pointer to a real function, e.g.
142+
</p>
143+
<blockquote><pre>
144+
R(*)(ArgTypes...) target_func_ptr() const noexcept;
145+
</pre></blockquote>
146+
</discussion>
147+
148+
<resolution>
149+
<p>This wording is relative to N4567.</p>
150+
151+
<ol>
152+
<li><p>Change <sref ref="[func.wrap.func.targ]"/> p2 as indicated:</p>
153+
154+
<blockquote><pre>
155+
template&lt;class T&gt; T* target() noexcept;
156+
template&lt;class T&gt; const T* target() const noexcept;
157+
</pre>
158+
<blockquote>
159+
<p>
160+
<del>-2- <i>Requires</i>: <tt>T</tt> shall be a type that is <tt>Callable</tt> (<sref ref="[func.wrap.func]"/>) for parameter types
161+
<tt>ArgTypes</tt> and return type <tt>R</tt>.</del>
162+
<p/>
163+
-3- <i>Returns</i>: If <tt>target_type() == typeid(T)</tt> a pointer to the stored function target; otherwise a null
164+
pointer.
165+
</p>
166+
</blockquote>
167+
</blockquote>
168+
</li>
169+
170+
</ol>
171+
</resolution>
172+
173+
</issue>

0 commit comments

Comments
 (0)