This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++23 status.

3474. Nesting join_views is broken because of CTAD

Section: 26.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 (26.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 26.7.14.1 [range.join.overview] p2

The name views::join denotes a range adaptor object (26.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 26.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 26.7.14.1 [range.join.overview] as indicated:

    -2- The name views::join denotes a range adaptor object (26.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 26.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 26.7.14.1 [range.join.overview] as indicated:

    -2- The name views::join denotes a range adaptor object (26.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}.