<!DOCTYPE HTML>
<html>
<head>
	<title>Making multi-param (and other converting) constructors of views explicit</title>

	<style>
	p {text-align:justify}
	li {text-align:justify}
	blockquote.note
	{
		background-color:#E0E0E0;
		padding-left: 15px;
		padding-right: 15px;
		padding-top: 1px;
		padding-bottom: 1px;
	}
	ins {color:#00A000}
	del {color:#A00000}
	</style>
</head>
<body>

<address align=right>
Document number: P2711R0
<br/>
Audience: LEWG, LWG
<br/>
<br/>
<a href="mailto:ville.voutilainen@gmail.com">Ville Voutilainen</a><br/>
2022-11-09<br/>
</address>
<hr/>
<h1 align=center>Ruminations on explicit multi-param constructors of views</h1>

<h2>Abstract</h2>

<p>This paper is about <a href="https://cplusplus.github.io/LWG/issue3714">LWG 3714</a>, Non-single-argument constructors for range adaptors should not be explicit.
</p>
<p>We have C++20 views, none of which have explicit multi-param constructors,
  and some newer C++23 views which do. This is an obscure and rarely-noticeable
  difference. This paper looks at some aspects of it.
</p>

<h2>Spoiler</h2>

<p>
  SG9 decided to go for making the C++20 views' constructors explicit
  as well, even though that's a breaking change.
</p>
<p>
<p><pre>POLL: We support applying "LWG3714: Non-single-argument constructors for range adaptors should not be explicit" AKA option 2 in Ville's paper: "Drop the explicit from the C++23 views" to C++23 (possibly as an NB comment, if needed)
SF	F	N	A	SA
1	4	3	2	0</pre>
</p>
<p>
  Despite keeping all the original rumination, this paper contains wording
  for doing exactly that. As an addition, while looking at the wording, I became quite puzzled why any of the _single_-argument constructors should be
  implicit, and that would be yet another inconsistency, so the wording
  in this paper fixes those too for the C++20 views.
</p>

<h2>Why do we care?</h2>

<p>
  That's an interesting question. By and large, we don't, and we don't want
  to. Most if not all code should _always_ use views::foo, instead of
  constructing a foo_view. I personally don't find the examples
  in the issue convincing at all, and from what I gather, neither
  does anyone else, much. But we have a difference, and whether
  it's explicable, necessary, or useful, is.. ..somewhat questionable.
</p>

<p>Let's dive right in, then. In the issue, we have an example thus:</p>
<blockquote><pre><code>std::ranges::chunk_view r1 = {v, 1}; // ill-formed</code></pre></blockquote>

<p>chunk_view's multi-param constructor is explicit, so you can't do that.
  You can do it for a filter_view, for example, as mentioned in the issue,
  because there the constructor isn't explicit.</p>

<p>Okay, fine, one could write, instead, this:</p>
<blockquote><pre><code>std::ranges::chunk_view r1{v, 1}; // now it's fine</code></pre></blockquote>

<p>Cool. _We_ all know that that's vastly different, the explicit constructor
  forces us to name the type when we initialize an object using an explicit
  constructor.. ..but to a naive reader, that's still just a one-character
  difference, "drop the '=' and it's fine".</p>

<p>For this particular example, I find it highly dubious what extra
  protection we're really providing with the explicit constructor.</p>

<p>Okay, fine, what about a function parameter? We could have
  an example like thus:</p>

<blockquote><pre><code>template &lt;class V&gt; void f(std::ranges::chunk_view&lt;V&gt; p);
f({v, 1});
</code></pre></blockquote>

<p>
  This is ill-formed regardless of whether the constructor is explicit,
  because template deduction from a plain braced-list doesn't happen. So in
  this case, an implicit constructor doesn't provide convenience, and an
  explicit constructor doesn't provide particular protection.
</p>

<p>Okay, let's try hard. Let's try really really really hard to come up with a remotely
  explicable example, emphasis on "remotely":

<blockquote><pre><code>#include &lt;vector&gt;
#include &lt;ranges&gt;

using my_filter_view = decltype(std::views::filter(std::declval&lt;std::vector&lt;int&gt;&&gt;(), std::declval&lt;bool (*)(int)&gt;()));

void f(my_filter_view) {}

int main() {
    std::vector&lt;int&gt; v;
    f({v, +[](int x) -> bool {return x % 2;}});
}
</code></pre></blockquote>

<p>Congratulations to me, here we have a non-template function taking
  a filter on a vector of ints, and we can pass in multiple different
  predicates, because the function takes a filter that uses a bool(*)(int).
  Lambdas converted to function pointers work fine. We can even bake
  f() into an ABI, and now we can use unnamed/untyped brace-init.
  We couldn't do that if we used a chunk_view instead of filter_view,
  because there the constructor is explicit.
</p>

<h2>So _that's your convincing example?</h2>

<p>
  No. :) Not at all. :) I wouldn't recommend to anyone to write something
  like that. I would recommend keeping view types out of ABIs, or at
  worst, using a type-erased view in an ABI. But something like that.. as
  I said, we're trying really hard, to come up with something _remotely_
  plausible. It required some serious effort, and the result isn't
  all that plausible, if you ask me.
</p>

<p>However.</p>

<p>I find it similarly hard to come up with a plausible example where
  the explicit constructor is truly _useful_, so that it protects
  innocent users from making mistakes. As things are, it seems to require
  some serious effort to write code where that presumed protection
  ends up protecting an innocent user.</p> 


<h2>An attempt at a summary of thoughts</h2>

<p>
  It sure seems to me that to be explicit or not
  <ul>
    <li>certainly can be seen as supporting code that we wouldn't recommend anyone to write in the first place</li>
    <li>but it also doesn't seem to offer meaningful protection for code that we expect anyone to write</li>
  </ul>
  so we seem to.. ..be protecting against mistakes that shouldn't ever occur
  to begin with.
</p>

<p>So we certainly have a choice to make here:
  <ol>
    <li>Make the C++20 view multi-param constructors explicit too, if we believe the protection is worthwhile</li>
    <li>Drop the explicit from the C++23 views, if we don't believe the
      protection is worthwhile.</li>
  </ol>
  To me, the whole issue seems super-obscure, and pedantic to the hilt. There seems to be various differences of opinion whether we should care at all, but
  I find it equally plausible to consider the different treatise of explicit
  in the constructors of different views just.. ..weird. Yeah, sure, it's
  just an inconsistency. The practical impact should be extremely small. But
  in the same vein and in the same breath, that calls into question what
  the purpose and importance of using explicit where it wasn't used before
  really is.
</p>

<p>I personally can't get excited over this either way. I could live with
  both adding explicit to the C++20 views, or with dropping it from the C++23
  views. I don't think it's really explicable to have this sort of a difference,
  but I find it hard to believe it's a significant matter that should
  necessarily have high importance. In certain ways, I'd really prefer
  that we go either way soon, and be done with it.</p>

<h2>Wording</h2>

<p>Drafting Note: the intent here is to make every constructor that is
  not a special member function of every range view in the standard explicit.
</p>
<p>Including single-parameter constructors. The original discussion
  was about multi-parameter constructors, but we want API design consistency
  here, and don't want to introduce a different inconsistency while
  fixing another.</p>

<p>--End Drafting Note.</p>

<p>
  In [range.iota.view] synopsis, add explicit:</p>
<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> iota_view(type_identity_t&lt;W&gt; value, type_identity_t&lt;Bound&gt; bound);
constexpr <ins>explicit</ins> iota_view(iterator first, see below last);
</code></pre></blockquote>          
</p>

<p>
  Before [range.iota.view]/8, add explicit:<p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> iota_view(type_identity_t&lt;W&gt; value, type_identity_t&lt;Bound&gt; bound);
</code></pre></blockquote>          
</p>

<p>
  Before [range.iota.view]/10, add explicit:<p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> iota_view(iterator first, see below last);
</code></pre></blockquote>          
</p>

<p>In [range.ref.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> ref_view(T&& t);
</code></pre></blockquote>          
</p>

<p>Before [range.ref.view]/2, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> ref_view(T&& t);
</code></pre></blockquote>          
</p>

<p>In [range.owning.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> owning_view(R&& t);
</code></pre></blockquote>          
</p>

<p>Before [range.owning.view]/2, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> owning_view(R&& t);
</code></pre></blockquote>          
</p>

<p>In [range.filter.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> filter_view(V base, Pred pred);</code></pre></blockquote>          
</p>

<p>Before [range.filter.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> filter_view(V base, Pred pred);</code></pre></blockquote>          
</p>

<p>In [range.transform.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> transform_view(V base, F fun);
</code></pre></blockquote>          
</p>

<p>Before [range.transform.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> transform_view(V base, F fun);
</code></pre></blockquote>          
</p>

<p>In [range.take.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> take_view(V base, range_difference_t<V> count);</code></pre></blockquote>          
</p>

<p>Before [range.take.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> take_view(V base, range_difference_t<V> count);</code></pre></blockquote>          
</p>

<p>In [range.take.while.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> take_while_view(V base, Pred pred);
</code></pre></blockquote>          
</p>

<p>Before [range.take.while.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> take_while_view(V base, Pred pred);
</code></pre></blockquote>          
</p>

<p>In [range.drop.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> drop_view(V base, range_difference_t<V> count);</code></pre></blockquote>          
</p>

<p>Before [range.drop.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> drop_view(V base, range_difference_t<V> count);</code></pre></blockquote>          
</p>

<p>In [range.drop.while.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> drop_while_view(V base, Pred pred);</code></pre></blockquote>          
</p>

<p>Before [range.drop.while.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> drop_while_view(V base, Pred pred);</code></pre></blockquote>          
</p>

<p>In [range.join.with.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> join_with_view(V base, Pattern pattern);
..
constexpr <ins>explicit</ins> join_with_view(R&& r, range_value_t&lt;InnerRng&gt; e);      
</code></pre></blockquote>          
</p>

<p>Before [range.join.with.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> join_with_view(V base, Pattern pattern);
</code></pre></blockquote>          
</p>

<p>Before [range.join.with.view]/2, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> join_with_view(R&& r, range_value_t&lt;InnerRng&gt; e);      
</code></pre></blockquote>          
</p>

<p>In [range.lazy.split.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> lazy_split_view(V base, Pattern pattern);
...
constexpr <ins>explicit</ins> lazy_split_view(R&& r, range_value_t&lt;R&gt; e);       
</code></pre></blockquote>          
</p>

<p>Before [range.lazy.split.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> lazy_split_view(V base, Pattern pattern);
</code></pre></blockquote>          
</p>

<p>Before [range.lazy.split.view]/2, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> lazy_split_view(R&& r, range_value_t&lt;R&gt; e);       
</code></pre></blockquote>          
</p>

<p>In [range.split.view] synopsis, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> split_view(V base, Pattern pattern);
...        
constexpr <ins>explicit</ins> split_view(R&& r, range_value_t&lt;R&gt; e);
</code></pre></blockquote>          
</p>

<p>Before [range.split.view]/1, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> split_view(V base, Pattern pattern);
</code></pre></blockquote>                  
</p>

<p>Before [range.split.view]/2, add explicit:</p>

<p>
<blockquote><pre><code>constexpr <ins>explicit</ins> split_view(R&& r, range_value_t&lt;R&gt; e);
</code></pre></blockquote>          
</p>

</body>
</html>
