3474. Nesting join_views is broken because of CTAD

Section: 25.7.14 [range.join] Status: C++23 Submitter: Barry Revzin Opened: 2020-08-04 Last modified: 2023-11-22

Priority: Not Prioritized

View all other issues in [range.join].

View all issues with C++23 status.

Discussion:

Let's say I had a range of range of ranges and I wanted to recursively flatten it. That would involve repeated invocations of join. But this doesn't work:

std::vector<std::vector<std::vector<int>>> nested_vectors = {
  {{1, 2, 3}, {4, 5}, {6}},
  {{7},       {8, 9}, {10, 11, 12}},
  {{13}}
};
auto joined = nested_vectors | std::views::join | std::views::join;

The expectation here is that the value_type of joined is int, but it's actually vector<int> — because the 2nd invocation of join ends up just copying the first. This is because join is specified to do:

The name views::join denotes a range adaptor object (25.7.2 [range.adaptor.object]). Given a subexpression E, the expression views::join(E) is expression-equivalent to join_view{E}.

And join_view{E} for an E that's already a specialization of a join_view just gives you the same join_view back. Yay CTAD. We need to do the same thing with join that we did with reverse in P1252. We can do that either in exposition (Option A) my modifying 25.7.14.1 [range.join.overview] p2

The name views::join denotes a range adaptor object (25.7.2 [range.adaptor.object]). Given a subexpression E, the expression views::join(E) is expression-equivalent to join_view<views::all_t<decltype((E))>>{E}.

Or in code (Option B) add a deduction guide to 25.7.14.2 [range.join.view]:

  template<class R>
    explicit join_view(R&&) -> join_view<views::all_t<R>>;

  template<class V>
    explicit join_view(join_view<V>) -> join_view<join_view<V>>;

[2020-08-21; Issue processing telecon: Option A is Tentatively Ready]

Previous resolution [SUPERSEDED]:

This wording is relative to N4861.

[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]

Option A:

  1. Modify 25.7.14.1 [range.join.overview] as indicated:

    -2- The name views::join denotes a range adaptor object (25.7.2 [range.adaptor.object]). Given a subexpression E, the expression views::join(E) is expression-equivalent to join_view<views::all_t<decltype((E))>>{E}.

Option B:

  1. Modify 25.7.14.2 [range.join.view] as indicated:

    namespace std::ranges {
      […]
      
      template<class R>
        explicit join_view(R&&) -> join_view<views::all_t<R>>;
      
      template<class V>
        explicit join_view(join_view<V>) -> join_view<join_view<V>>;
    }
    

[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]

Proposed resolution:

This wording is relative to N4861.

  1. Modify 25.7.14.1 [range.join.overview] as indicated:

    -2- The name views::join denotes a range adaptor object (25.7.2 [range.adaptor.object]). Given a subexpression E, the expression views::join(E) is expression-equivalent to join_view<views::all_t<decltype((E))>>{E}.