<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<style type="text/css">
pre {font-family: "Consolas", "Lucida Console", monospace; margin-left:20pt; }
code {font-family: "Consolas", "Lucida Console", monospace; }
pre > i   { font-family: "Consolas", "Lucida Console", monospace;  font-style:italic; }
code > i  { font-family: "Consolas", "Lucida Console", monospace;  font-style:italic; }
pre > em  { font-family: "Consolas", "Lucida Console", monospace;  font-style:italic; }
code > em { font-family: "Consolas", "Lucida Console", monospace;  font-style:italic; }
body { color: #000000; background-color: #FFFFFF; }
del { text-decoration: line-through; color: #8B0040; }
ins { text-decoration: underline; color: #005100; }

p.example   { margin-left: 2em; }
pre.example { margin-left: 2em; }
div.example { margin-left: 2em; }

code.extract { background-color: #F5F6A2; }
pre.extract  { margin-left: 2em; background-color: #F5F6A2;  border: 1px solid #E1E28E; }

p.function    { }
.attribute    { margin-left: 2em; }
.attribute dt { float: left; font-style: italic;  padding-right: 1ex; }
.attribute dd { margin-left: 0em; }

blockquote.std    { color: #000000; background-color: #F1F1F1;  border: 1px solid #D1D1D1;  padding-left: 0.5em; padding-right: 0.5em; }
blockquote.stddel { text-decoration: line-through;  color: #000000; background-color: #FFEBFF;  border: 1px solid #ECD7EC;  padding-left: 0.5empadding-right: 0.5em; ; }
blockquote.stdins { text-decoration: underline;  color: #000000; background-color: #C8FFC8;  border: 1px solid #B3EBB3; padding: 0.5em; }
table.header { border: 0px; border-spacing: 0;  margin-left: 0px; font-style: normal; }
table { border: 1px solid black; border-spacing: 0px;  margin-left: auto; margin-right: auto; }
th { text-align: left; vertical-align: top;  padding-left: 0.4em; border: none;  padding-right: 0.4em; border: none; }
td { text-align: left;  padding-left: 0.4em; border: none;  padding-right: 0.4em; border: none; }

.revision   { /*color: #005599;*/ }

</style>

<title>Are default function arguments in the immediate context?</title>

</head>
<body>

<table class="header"><tbody>
  <tr>
    <th>Document number:&nbsp;&nbsp;</th><th> </th><td>P2285R0</td>
  </tr>
  <tr>
    <th>Date:&nbsp;&nbsp;</th><th> </th><td>2021-01-14</td>
  </tr>
  <tr>
    <th>Audience:&nbsp;&nbsp;</th><th> </th><td>EWG</td>
  </tr>
  <tr>
    <th>Reply-to:&nbsp;&nbsp;</th><th> </th><td><address>Andrzej Krzemie&#324;ski &lt;akrzemi1 at gmail dot com&gt;</address>
        <address>Tomasz Kami&#324;ski &lt;tomaszkam at gmail dot com&gt;</address></td>
  </tr>
</tbody></table>



<h1>Are default function arguments in the immediate context?</h1>


<p>In this document we explore the question if evaluating the default function arguments should be considered the
   immediate context. In other words, if the following program should be well formed or not:</p>

<pre>template &lt;typename T, typename Allocator&gt;
struct container
{
  template &lt;std::ranges::range Range&gt;
  explicit container(Range r, Allocator a = Allocator()) {}
};

struct Alloc
{
  Alloc() = delete;
  <em>// ...</em>
};

int main()
{
  constexpr bool c = std::constructible_from&lt;container&lt;int, Alloc&gt;, std::vector&lt;int&gt;&gt;;
  std::cout &lt;&lt; c &lt;&lt; std::endl;
}</pre>

<p>Currently, compilers give different answers. (For the purpose of the test we replaced concept
<code>std::constructible_from</code> with type trait <code>std::is_constructible</code>.)
Clang and ICC fail to compile: the evaluation of the concept (trait) is ill-formed. GCC outputs 0. MSVC outputs 1.
</p>

<p>This addresses issue <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#2296">CWG 2296</a>.</p>


<p>Tony table:</p>

<table>
<tr><th>Today</th><th>This proposal</th></tr>
<tr>
<td valign="top"><pre>struct Map
{
  explicit Map(Range&amp;&amp;, Hash, Equal, Allocator);

  explicit Map(Range&amp;&amp; c, Hash h, Equal e)
   requires default_initializable&lt;Allocator>
   : Map(c, h, e, Allocator()) {}

  explicit Map(Range&amp;&amp; c, Hash h)
   requires default_initializable&lt;Equal>
         &amp;&amp; default_initializable&lt;Allocator>
   : Map(c, h, Equal(), Allocator()) {}

  explicit Map(Range&amp;&amp; c)
   requires default_initializable&lt;Hash>
         &amp;&amp; default_initializable&lt;Equal>
         &amp;&amp; default_initializable&lt;Allocator>
   : Map(c, Hash(), Equal(), Allocator()) {}
};</pre></td>
<td valign="top"><pre>struct Map
{
  explicit Map(Range&amp;&amp;, Hash = Hash(), Equal = Equal(), Allocator = Allocator());
};</pre></td>
</tr>
</table>



<h2><a name="discussion">1. Discussion</a></h2>

<h3><a name="discussion.model">1.1. Conceptual model for default function arguments</a></h3>

<p>An important question that we would like to be sorted out is what is the conceptual model behind default function arguments. Is it:</p>

<ol>
  <li>Additional function overloads that share implementation.</li>
  <li>The "injection" of missing arguments into the function call.</li>
</ol>

<p>Consider the following declaration</p>

<pre>int f(int i, int j = make_j());</pre>

<p>The first model ("additional overloads") would mean that the above is somewhat equivalent to the following two declarations:</p>

<pre>int f(int i, int j);
inline int f(int i) { return f(i, make_j()); }</pre>

<p>The second model ("inject missing arguments") would mean that whenever function <code>f</code> is called with one argument:</p>

<pre>f(3);</pre>

<p>It is replaced with:</p>

<pre>f(3, make_j());</pre>

<p>If we can describe the semantics of default function arguments using one of the above models, it should make it clear for the
programmers how the feature behaves in different contexts.</p>


<h4><a name="discussion.model.address">1.1.1. Function address</a></h4>

<p>Given the following code:</p>

<pre>template &lt;typename F&gt;
void test(F);

int f(int i, int j = make_j());

int main()
{
  test(&amp;f);
}</pre>

<p>Under model "inject missing arguments" we have only one unambiguous function <code>f</code>, so the program should compile.
In contrast, under model "additional overloads" expression <code>&amp;f</code> does not refer to a single function, so the call to <code>test</code>
is ambiguous. In this case the current rules clearly prefer the "inject missing arguments" model.</p>

<p>A related question is whether we should be able to cast a function with default arguments to a function pointer of type with fewer arguments:</p>

<pre>int f(int i, int j = make_j());

using fun_ptr = int(*)(int);
fun_ptr p = f;</pre>

<p>This reflects an expectation (not necessariy a correct one) that if we add a new argument to a function with a default value,
   any program should still compile (and have the same semantics). Under model "inject missing arguments" the above program fails to compile
   because there is only one <code>f</code>, and it takes two arguments. Under model "additional overloads" we have two overloads, and one of them is
   a perfect match. In this case, again, the current rules clearly prefer the "inject missing arguments" model.</p>


<h4><a name="discussion.model.address">1.1.2. Function's source location</a></h4>

<p>Given the following code:</p>

<pre>int f(int i, int j = std::source_location::current().line()); <em>// line A</em>

int main()
{
  f(1); <em>// line B</em>
}</pre>

<p>Should the value of parameter <code>j</code> be initialized to line A or line B? Model "inject missing parameters" suggests line B,
 and this is what the Standard requires.</p>


<h4><a name="discussion.model.lookup">1.1.3. How the names are looked up</a></h4>

<p>Consider the following example:</p>

<pre><em>// included from header file:</em>

const int width = 4;

void foo(int val, int w = width) { std::cout &lt;&lt; std::string(w, '=') &lt;&lt; val &lt;&lt; " "; }

<em>// in a cpp file:</em>

int main()
{
  for (int width : {1, 2, 3})
    foo(width);
}</pre>

<p> In model "additional overloads" this program outputs <code>"====1 ====2 ====3 "</code>.
 In model "inject missing parameters" this program outputs <code>"=1 ==2 ===3 "</code>
 because what is injected is the nested <code>width</code>: the one declared inside the <code>for</code>-loop.
 </p>


<h4><a name="discussion.model.immediate">1.1.4. Being in immediate context</a></h4>

<p>Finally, the question of being in immediate context. In model "inject missing parameters" the expressions injected
in the call site clearly become part of immediate context. On the other hand, in mode "additional overloads" the expressions
for computing missing parameters are part of function bodies and are not part of immediate context.</p>


<h3><a  name="discussion.member_init">1.2. Consistency with default member initializers</a></h3>

<p>We believe that whatever solution is applied to default function arguments, it should also be applied to defult member initializers.
   This is for consistency rasons, as the two feturesare similar:</p>

<pre>template &lt;typename T&gt;
struct S
{
  T x;
  T y = T{};

  <em>// explicit S(T x, T y = T{}) : x{x} y{y} {}</em>
};

struct NoDefault
{
  explicit NoDefault(int) {}
};

int main()
{
  std::cout &lt;&lt; std::constructible_from&lt;C, NoDefault&gt; &lt;&lt; std::endl;
}</pre>

<p>The answer to the question whether this program compiles and what value is printed shouls not change if we uncomment the constructor in class <code>S</code>.</p>

<p>Currently, compilers vary in whether they treat default member initializers as an immediate context. Consider the following example:</p>

<pre>struct NoDefault
{
  explicit NoDefault(int) {}
};

template&lt;typename T&gt;
struct S
{
  int x;
  T y = T{};
};

template &lt;typename T&gt;
int convertible(...) { return 0; }

template &lt;typename T&gt;
auto convertible(int) -&gt; decltype(S&lt;T&gt;{2}.x) { return 1; }

int main()
{
   std::cout &lt;&lt; convertible&lt;NoDefault&gt;(1) &lt;&lt; std::endl;
}</pre>

<p>Currently, GCC and MSVC output 0, Clang and ICC fail to compile.</p>


<h3><a name="discussion.applicability">1.3. Applicability of default function arguments</a></h3>


<p>Is there sufficient motivation to add a requirement that default function arguments be in the
 immediate context of the function call? We believe there is. With C++20's concepts quetying for types'
properties and designing conditional interfaces will become more common and less expert-only. We already
 have features like <code>explicit(bool)</code> introduced primarily for rendering non-uniform
 instantations of class templates based on the properties of template arguments.</p>

<p>Now, with the addition of ranges library to C++, we may want to introduce container constructors that take ranges as arguments.
In practice, for a container like <code>unordered_map</code> we will need variations that take a hash, a comparator, an allocator.
This could be handled by one constructor declaration with default function arguments:</p>

<pre>template &lt;class Key, class Hash = /**/, class KeyEqual = /**/, class Allocator = /**/ &gt;
class unordered_set;
{
  explicit unordered_set(range auto&amp;&amp;, Hash = Hash(), KeyEqual = KeyEqual(), Allocator = Allocator());
};</pre>

<p>But will <code>constructible_from&lt;unordered_map&lt;int&gt;, MyIntHash&gt;, std::vector&lt;int&gt;&gt;</code> give the true answer?</p>

<p>Unless default function arguments are in the immediate context, if we want concept <code>std::constructible_from</code> to return the true answer,
we will have to provide a number of constructors:</p>

<pre>template &lt;class Key, class Hash = /**/, class KeyEqual = /**/, class Allocator = /**/ &gt;
class unordered_set;
{
  explicit unordered_set(range auto&amp;&amp;, Hash, KeyEqual, Allocator);

  explicit unordered_set(range auto&amp;&amp; c, Hash h, KeyEqual e)
   requires default_initializable&lt;Allocator>
   : unordered_set(c, h, e, Allocator()) {}

  explicit unordered_set(range auto&amp;&amp; c, Hash h)
   requires default_initializable&lt;KeyEqual>
         &amp;&amp; default_initializable&lt;Allocator>
   : unordered_set(c, h, KeyEqual(), Allocator()) {}

  explicit unordered_set(range auto&amp;&amp; c)
   requires default_initializable&lt;Hash>
         &amp;&amp; default_initializable&lt;KeyEqual>
         &amp;&amp; default_initializable&lt;Allocator>
   : unordered_set(c, Hash(), KeyEqual(), Allocator()) {}

};</pre>

<p>But if we did so, one could ask, what are default function argument for, if the Standard Library cannot use them?</p>

<p>Alternatively, we can ask, is the above case with <code>unordered_map</code> something that default function arguments are supposed to handle?</p>


<h2><a name="recommendation">2. Our Recommendation</a></h2>

<p>We recommend that the model for default function arguments as well as for default member initializers is "inject missing arguments" except for one aspect:
    name lookup is performed as if it was inside the function body (as in "additional overloads").</p>


<h2><a name="wording">3. Wording</a></h2>

<p>TBD.</p>


<h2><a name="acknowledgments">4. Acknowledgments</a></h2>

<p>Tim Song indicated the full scale of the problem with default function arguments, which motivated the scope and shape of this paper.</p>

<p>We are grateful to Hubert Tong and David Vandevoorde for their valuable input.</p>


<h2><a name="literature">5. References</a></h2>

<ul>

  <li>[P0348R0] &mdash; Andrzej Krzemie&#324;ski, "Validity testing issues" <br>
    (<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0348r0.html"
    >http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0348r0.html</a>).
    </li>

  <li>[P1073R2] &mdash; Richard Smith, Andrew Sutton, Daveed Vandevoorde, "Immediate functions" <br>
    (<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r2.html"
    >http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r2.html</a>).
    </li>

</ul>



</body>
</html>
