|
| 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ü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&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 <functional> |
| 58 | +#include <iostream> |
| 59 | +#include <typeinfo> |
| 60 | +#include "boost/function.hpp" |
| 61 | + |
| 62 | +void foo(int) {} |
| 63 | + |
| 64 | +int main() { |
| 65 | + std::function<void(int)> f(foo); |
| 66 | + std::cout << f.target<void(*)()>() << std::endl; |
| 67 | + boost::function<void(int)> f2(foo); |
| 68 | + std::cout << f2.target<void(*)()>() << 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<class D, class T> |
| 89 | +D* get_deleter(const shared_ptr<T>& 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<T>()</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<T></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<void()> f = foo; |
| 121 | + if (f.target<decltype(foo)>()) { |
| 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<class T> T* target() noexcept; |
| 156 | +template<class T> 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