From 4dfde1b62a3a7c9adad4c699509b9813458372e5 Mon Sep 17 00:00:00 2001 From: huixie90 Date: Fri, 16 Aug 2024 17:36:33 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20huixie90?= =?UTF-8?q?/cpp=5Fpapers@f00c26dee87dd378f4c0d31c97c29fb01d171c75=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- any_view.md | 65 +++++--- generated/any_view.html | 326 +++++++++++++++++++++------------------- 2 files changed, 210 insertions(+), 181 deletions(-) diff --git a/any_view.md b/any_view.md index 18503e8..1cecca6 100644 --- a/any_view.md +++ b/any_view.md @@ -21,19 +21,34 @@ toc: true # Abstract -This paper proposes a new type-erased `view`: `any_view`. +This paper proposes a new type-erased view: `std::ranges::any_view`. +That type-erased view allows customizing the traversal category of the view, +its value type and a few other properties. For example: -# Motivation and Examples +```cpp +class MyClass { + std::unordered_map widgets_; +public: + std::ranges::any_view getWidgets(); +}; + +std::ranges::any_view MyClass::getWidgets() { + return widgets_ | std::views::values + | std::views::filter(myFilter); +} +``` + +# Motivation Since being merged into C++20, the Ranges library has been enjoying a -contribution of ever richer set of expressive `view`s. For example, +contribution of ever richer set of expressive views. For example, ```cpp // in MyClass.hpp -class MyClass{ +class MyClass { std::unordered_map widgets_; public: - auto getWidgets () { + auto getWidgets() { return widgets_ | std::views::values | std::views::filter(myFilter); } @@ -105,7 +120,7 @@ erasure capability to own or reference any object of any type that satisfies the `ranges::range` concept itself, or any further refinement via customizable constraints on its traversal categories and other range characteristics. -# Design Questions and Prior Art +# Design Space and Prior Art Designing a type like `any_view` raises a lot of questions. @@ -210,7 +225,17 @@ template , class RValueRef = add_rvalue_reference_t>, class Diff = ptrdiff_t> -class any_view; +class any_view { + // TODO: Include a rough synopsis for the class. Pseudo-code like `// only when sized_range` is OK. + // This part can be removed/simplified once you have actual wording. + + size_t size() const; // only when Cat includes `any_view_category::sized` + + using iterator = /* exposition-only */; + using sentinel = /* exposition-only */; + + // etc... +}; ``` The intent is that users can select various desired properties of the `any_view` by `bitwise-or`ing them. For example: @@ -219,13 +244,11 @@ The intent is that users can select various desired properties of the `any_view` using MyView = std::ranges::any_view; ``` -# Design Considerations +# Other Design Considerations ## Should the first argument be `Ref` or `Value`? -### Option 1 - -If the first template parameter is `Ref`, +If the first template parameter is `Ref`, we have: ```cpp template However, it is possible that the user uses `any_view` without realizing that they specified the reference type and they now make a copy of the `string` every time when the iterator is dereferenced. -### Option 2 - -If the first template parameter is `Value`, +Instead, if the first template parameter is `Value`, we have: ```cpp template ``` -### Author Recommendation - -Even though Option 1 is less verbose in few cases, it might create unnecessary copies without user realizing it. The author recommends that Option 2 is preferable. +**Author Recommendation**: Even though the first option is less verbose in few cases, it might create unnecessary copies without user realizing it. The author recommends the second option. ## Name of the `any_view_category` @@ -381,7 +400,7 @@ vs } ``` -#### -O0 +**-O0** ```bash Benchmark Time Time vector Time any_view @@ -399,7 +418,7 @@ OVERALL_GEOMEAN +3.4278 0 ``` -#### -O2 +**-O2** ```bash Benchmark Time Time vector Time any_view @@ -464,7 +483,7 @@ And this is what we are going to measure: In the `any_view` case, we simply replace `std::ranges::transform_view` by `std::ranges::any_view` and we measure the same thing. -#### -O0 +**-O0** ```bash Benchmark Time Time complicated Time any_view @@ -481,7 +500,7 @@ Benchmark Time Time OVERALL_GEOMEAN +0.4120 0 0 ``` -#### -O2 +**-O2** ```bash Benchmark Time Time complicated Time any_view @@ -530,7 +549,7 @@ std::vector UI::getWidgetNames() const { ``` -#### -O0 +**-O0** ```bash Benchmark Time Time vector Time any_view @@ -547,7 +566,7 @@ Benchmark Time Time v OVERALL_GEOMEAN -0.4917 0 0 ``` -#### -O2 +**-O2** ```bash Benchmark Time Time vector Time any_view diff --git a/generated/any_view.html b/generated/any_view.html index 5de3330..2a73893 100644 --- a/generated/any_view.html +++ b/generated/any_view.html @@ -458,9 +458,8 @@

Contents

  • 1.1 R0
  • 2 Abstract
  • -
  • 3 Motivation and -Examples
  • -
  • 4 Design Questions and Prior +
  • 3 Motivation
  • +
  • 4 Design Space and Prior Art
  • 5 Proposed Design
  • -
  • 6 -Design Considerations +
  • 6 Other Design +Considerations

    2 Abstract

    -

    This paper proposes a new type-erased -view: -any_view.

    -

    3 Motivation and Examples

    +

    This paper proposes a new type-erased view: +std::ranges::any_view. That +type-erased view allows customizing the traversal category of the view, +its value type and a few other properties. For example:

    +
    class MyClass {
    +  std::unordered_map<Key, Widget> widgets_;
    +public:
    +  std::ranges::any_view<Widget> getWidgets();
    +};
    +
    +std::ranges::any_view<Widget> MyClass::getWidgets() {
    +  return widgets_ | std::views::values
    +                  | std::views::filter(myFilter);
    +}
    +

    3 Motivation

    Since being merged into C++20, the Ranges library has been enjoying a -contribution of ever richer set of expressive -views. For example,

    -
    // in MyClass.hpp
    -class MyClass{
    -  std::unordered_map<Key, Widget> widgets_;
    -public:
    -  auto getWidgets () {
    -    return widgets_ | std::views::values
    -                    | std::views::filter(myFilter);
    -  }
    -
    -  // other members
    -};
    +contribution of ever richer set of expressive views. For example,

    +
    // in MyClass.hpp
    +class MyClass {
    +  std::unordered_map<Key, Widget> widgets_;
    +public:
    +  auto getWidgets() {
    +    return widgets_ | std::views::values
    +                    | std::views::filter(myFilter);
    +  }
    +
    +  // other members
    +};

    While such use of ranges is exceedingly convenient, and indeed is the recommended and the intended use of the library, prudence is advisable when allowing such range definitions to “leak” away into the interface, @@ -584,11 +587,11 @@

    getWidgets is:

    -
    std::ranges::filter_view<
    -  std::ranges::elements_view<
    -    std::ranges::ref_view<std::unordered_map<Key, Widget>>,
    -    1>, 
    -  MyClass::getWidgets()::<lambda(const auto:11&)> >
    +
    std::ranges::filter_view<
    +  std::ranges::elements_view<
    +    std::ranges::ref_view<std::unordered_map<Key, Widget>>,
    +    1>, 
    +  MyClass::getWidgets()::<lambda(const auto:11&)> >

    Already hard to spell once, this expression template type is even harder to maintain against any evolution of the implementation of its business logic.

    @@ -622,7 +625,7 @@

    ranges::range concept itself, or any further refinement via customizable constraints on its traversal categories and other range characteristics.

    -

    4 Design Questions and Prior Art

    +

    4 Design Space and Prior Art

    Designing a type like any_view raises a lot of questions.

    @@ -686,14 +689,14 @@

    4.1 Boost.Range boost::ranges::any_range

    The type declaration is:

    -
    template<
    -    class Value
    -  , class Traversal
    -  , class Reference
    -  , class Difference
    -  , class Buffer = any_iterator_default_buffer
    ->
    -class any_range;
    +
    template<
    +    class Value
    +  , class Traversal
    +  , class Reference
    +  , class Difference
    +  , class Buffer = any_iterator_default_buffer
    +>
    +class any_range;

    It asks users to provide range_reference_t, range_value_t and @@ -713,19 +716,19 @@

    4.2 range-v3 ranges::views::any_view

    The type declaration is:

    -
    enum class category
    -{
    -    none = 0,
    -    input = 1,
    -    forward = 3,
    -    bidirectional = 7,
    -    random_access = 15,
    -    mask = random_access,
    -    sized = 16,
    -};
    -
    -template<typename Ref, category Cat = category::input>
    -struct any_view;
    +
    enum class category
    +{
    +    none = 0,
    +    input = 1,
    +    forward = 3,
    +    bidirectional = 7,
    +    random_access = 15,
    +    mask = random_access,
    +    sized = 16,
    +};
    +
    +template<typename Ref, category Cat = category::input>
    +struct any_view;

    Here Cat handles both the traversal category and sized_range. @@ -740,81 +743,88 @@

    5 Proposed Design

    This paper proposes the following interface:

    -
    enum class any_view_category
    -{
    -    none = 0,
    -    input = 1,
    -    forward = 3,
    -    bidirectional = 7,
    -    random_access = 15,
    -    contiguous = 31,
    -    mask = contiguous,
    -    sized = 32,
    -    borrowed = 64,
    -    move_only_view = 128
    -};
    -
    -template <class Ref,
    -          any_view_category Cat = any_view_category::input,
    -          class Value = decay_t<Ref>,
    -          class RValueRef = add_rvalue_reference_t<remove_reference_t<Ref>>,
    -          class Diff = ptrdiff_t>
    -class any_view;
    +
    enum class any_view_category
    +{
    +    none = 0,
    +    input = 1,
    +    forward = 3,
    +    bidirectional = 7,
    +    random_access = 15,
    +    contiguous = 31,
    +    mask = contiguous,
    +    sized = 32,
    +    borrowed = 64,
    +    move_only_view = 128
    +};
    +
    +template <class Ref,
    +          any_view_category Cat = any_view_category::input,
    +          class Value = decay_t<Ref>,
    +          class RValueRef = add_rvalue_reference_t<remove_reference_t<Ref>>,
    +          class Diff = ptrdiff_t>
    +class any_view {
    +  // TODO: Include a rough synopsis for the class. Pseudo-code like `// only when sized_range` is OK.
    +  // This part can be removed/simplified once you have actual wording.
    +
    +  size_t size() const; // only when Cat includes `any_view_category::sized`
    +
    +  using iterator = /* exposition-only */;
    +  using sentinel = /* exposition-only */;
    +
    +  // etc...
    +};

    The intent is that users can select various desired properties of the any_view by bitwise-oring them. For example:

    -
    using MyView = std::ranges::any_view<Widget, std::ranges::any_view_category::bidirectional | std::ranges::any_view_category::sized>;
    -

    6 Design Considerations

    +
    using MyView = std::ranges::any_view<Widget, std::ranges::any_view_category::bidirectional | std::ranges::any_view_category::sized>;
    +

    6 Other Design Considerations

    6.1 Should the first argument be Ref or Value?

    -

    6.1.1 Option 1

    If the first template parameter is -Ref,

    -
    template <class Ref,
    -          any_view_category Cat = any_view_category::input,
    -          class Value = decay_t<Ref>>
    +Ref, we have:

    +
    template <class Ref,
    +          any_view_category Cat = any_view_category::input,
    +          class Value = decay_t<Ref>>

    For a range with a reference to int, one would write

    -
    any_view<int&>
    +
    any_view<int&>

    And for a const reference to int, one would write

    -
    any_view<const int&>
    +
    any_view<const int&>

    In case of a generator range, e.g a transform_view which generates pr-value int, the usage would be

    -
    any_view<int>
    +
    any_view<int>

    However, it is possible that the user uses any_view<string> without realizing that they specified the reference type and they now make a copy of the string every time when the iterator is dereferenced.

    -

    6.1.2 Option 2

    -

    If the first template parameter is -Value,

    -
    template <class Value,
    -          any_view_category Cat = any_view_category::input,
    -          class Ref = Value&>
    +

    Instead, if the first template parameter is +Value, we have:

    +
    template <class Value,
    +          any_view_category Cat = any_view_category::input,
    +          class Ref = Value&>

    For a range with a reference to int, it would be less verbose

    -
    any_view<int>
    +
    any_view<int>

    However, in order to have a const reference to int, one would have to explicitly specify the Value, the any_view_category and finally the Ref, i.e.

    -
    any_view<int, any_view_category::input, const int&>
    +
    any_view<int, any_view_category::input, const int&>

    This is a bit verbose. In the case of a generator range, one would need to do the same:

    -
    any_view<int, any_view_category::input, int>
    -

    6.1.3 Author Recommendation

    -

    Even though Option 1 is less verbose in few cases, it might create -unnecessary copies without user realizing it. The author recommends that -Option 2 is preferable.

    +
    any_view<int, any_view_category::input, int>
    +

    Author Recommendation: Even though the first option +is less verbose in few cases, it might create unnecessary copies without +user realizing it. The author recommends the second option.

    6.2 Name of the any_view_category

    range-v3 uses the name @@ -958,9 +968,9 @@

    6.12 compiler to perform optimizations. With any_view, every iteration will have three indirect function calls:

    -
    ++it;
    -it != last;
    -*it;
    +
    ++it;
    +it != last;
    +*it;

    While it may at first seem prohibitive, it is useful to remember the context in which any_view is used and what the alternatives to it are.

    @@ -972,21 +982,21 @@

    any_view. For example, the following code:

    -
      std::vector v = std::views::iota(0, state.range(0)) | std::ranges::to<std::vector>();
    -  for (auto _ : state) {
    -    for (auto i : v) {
    -      benchmark::DoNotOptimize(i);
    -    }
    -  }
    -

    vs

      std::vector v = std::views::iota(0, state.range(0)) | std::ranges::to<std::vector>();
    -  std::ranges::any_view<int&> av(std::views::all(v));
    -  for (auto _ : state) {
    -    for (auto i : av) {
    -      benchmark::DoNotOptimize(i);
    -    }
    -  }
    -

    6.12.1.1 -O0

    + for (auto _ : state) { + for (auto i : v) { + benchmark::DoNotOptimize(i); + } + } +

    vs

    +
      std::vector v = std::views::iota(0, state.range(0)) | std::ranges::to<std::vector>();
    +  std::ranges::any_view<int&> av(std::views::all(v));
    +  for (auto _ : state) {
    +    for (auto i : av) {
    +      benchmark::DoNotOptimize(i);
    +    }
    +  }
    +

    -O0

    Benchmark                                           Time      Time vector         Time any_view
     -----------------------------------------------------------------------------------------------
     [BM_vector vs. BM_AnyView]/1024                  +3.4488            10423                 46371
    @@ -999,7 +1009,7 @@ 

    6.12.1.1< [BM_vector vs. BM_AnyView]/131072 +3.4295 1335405 5915230 [BM_vector vs. BM_AnyView]/262144 +3.4320 2665004 11811264 OVERALL_GEOMEAN +3.4278 0 0

    -

    6.12.1.2 -O2

    +

    -O2

    Benchmark                                           Time     Time vector      Time any_view
     -------------------------------------------------------------------------------------------
     [BM_vector vs. BM_AnyView]/1024                 +14.8383             315               4991
    @@ -1038,36 +1048,36 @@ 

    any_view or with the pipeline’s actual type:

    -
    // header file
    -struct Widget {
    -  std::string name;
    -  int size;
    -};
    -
    -struct UI {
    -  std::vector<Widget> widgets_;
    -  std::ranges::transform_view<complicated...> getWidgetNames();
    -};
    -
    -// cpp file
    -std::ranges::transform_view<complicated...> UI::getWidgetNames() {
    -  return widgets_ | std::views::filter([](const Widget& widget) {
    -           return widget.size > 10;
    -         }) |
    -         std::views::transform(&Widget::name);
    -}
    +
    // header file
    +struct Widget {
    +  std::string name;
    +  int size;
    +};
    +
    +struct UI {
    +  std::vector<Widget> widgets_;
    +  std::ranges::transform_view<complicated...> getWidgetNames();
    +};
    +
    +// cpp file
    +std::ranges::transform_view<complicated...> UI::getWidgetNames() {
    +  return widgets_ | std::views::filter([](const Widget& widget) {
    +           return widget.size > 10;
    +         }) |
    +         std::views::transform(&Widget::name);
    +}

    And this is what we are going to measure:

    -
      lib::UI ui = {...};
    -  for (auto _ : state) {
    -    for (auto& name : ui.getWidgetNames()) {
    -      benchmark::DoNotOptimize(name);
    -    }
    -  }
    +
      lib::UI ui = {...};
    +  for (auto _ : state) {
    +    for (auto& name : ui.getWidgetNames()) {
    +      benchmark::DoNotOptimize(name);
    +    }
    +  }

    In the any_view case, we simply replace std::ranges::transform_view<complicated...> by std::ranges::any_view and we measure the same thing.

    -

    6.12.2.1 -O0

    +

    -O0

    Benchmark                                                        Time      Time complicated    Time any_view
     ------------------------------------------------------------------------------------------------------------
     [BM_RawPipeline vs. BM_AnyViewPipeline]/1024                  +0.4290                 78469           112130
    @@ -1080,7 +1090,7 @@ 

    6.12.2. [BM_RawPipeline vs. BM_AnyViewPipeline]/131072 +0.4170 10142694 14372118 [BM_RawPipeline vs. BM_AnyViewPipeline]/262144 +0.4358 20386564 29270816 OVERALL_GEOMEAN +0.4120 0 0

    -

    6.12.2.2 -O2

    +

    -O2

    Benchmark                                                        Time      Time complicated    Time any_view
     ------------------------------------------------------------------------------------------------------------
     [BM_RawPipeline vs. BM_AnyViewPipeline]/1024                  +0.8066                  3504             6330
    @@ -1118,20 +1128,20 @@ 

    std::vector as a type erasure tool for lack of a better tool. Such code would look like this:

    -
    // header file
    -struct UI {
    -  std::vector<Widget> widgets_;
    -  std::vector<std::string> getWidgetNames() const;
    -};
    -
    -// cpp file
    -std::vector<std::string> UI::getWidgetNames() const {
    -  return widgets_ | std::views::filter([](const Widget& widget) {
    -           return widget.size > 10;
    -         }) |
    -         std::views::transform(&Widget::name) | std::ranges::to<std::vector>();
    -}
    -

    6.12.3.1 -O0

    +
    // header file
    +struct UI {
    +  std::vector<Widget> widgets_;
    +  std::vector<std::string> getWidgetNames() const;
    +};
    +
    +// cpp file
    +std::vector<std::string> UI::getWidgetNames() const {
    +  return widgets_ | std::views::filter([](const Widget& widget) {
    +           return widget.size > 10;
    +         }) |
    +         std::views::transform(&Widget::name) | std::ranges::to<std::vector>();
    +}
    +

    -O0

    Benchmark                                                       Time      Time vector<string>    Time any_view
     --------------------------------------------------------------------------------------------------------------
     [BM_VectorCopy vs. BM_AnyViewPipeline]/1024                  -0.5376                   238558           110316
    @@ -1144,7 +1154,7 @@ 

    6.12.3. [BM_VectorCopy vs. BM_AnyViewPipeline]/131072 -0.4792 27501856 14321826 [BM_VectorCopy vs. BM_AnyViewPipeline]/262144 -0.4838 55950048 28883803 OVERALL_GEOMEAN -0.4917 0 0

    -

    6.12.3.2 -O2

    +

    -O2

    Benchmark                                                       Time      Time vector<string>    Time any_view
     --------------------------------------------------------------------------------------------------------------
     [BM_VectorCopy vs. BM_AnyViewPipeline]/1024                  -0.8228                    35350             6264