Why does the C++23 ranges adaptor require a callable object to be copy_­constructible?
Asked Answered
M

1

11

Some ranges adaptors such as filter_­view, take_­while_­view and transform_view use std::optional's cousin copyable-box to store the callable object:

template<input_­range V, copy_­constructible F>
class transform_view : public view_interface<transform_view<V, F>> {
 private:
  V base_ = V();
  copyable-box<F> fun_;
};

which requires the callable F to be copy_­constructible, this also prevents us from passing in the callable that captures the move only object into transform_view (Godbolt):

#include <ranges>
#include <memory>

struct MoveOnlyFun {
  std::unique_ptr<int> x;
  MoveOnlyFun(int x) : x(std::make_unique<int>(x)) { } 
  int operator()(int y) const { return *x + y; }
};

int main() {
  auto r = std::views::iota(0, 5)
         | std::views::transform(MoveOnlyFun(1));
}

Since the view is not required to be copy_constructible, why do we require the callable to be copy_constructible? why don't we just use moveable-box to store callable instead of copyable-box? What are the considerations behind this?

Update:

The recent proposal P2494R0 also addresses this issue and proposes a detailed resolution.

Mensural answered 9/10, 2021 at 13:13 Comment(1)
Note: You are citing a draft standard of C++23 to talk about a C++20 issue. As the time between C++20's standardization and now passes, that draft will become more and more unlike C++20. In this case "copyable-box" is not even a thing in C++20. It uses semiregular-box.Brushwork
G
9

All the algorithms require copy-constructible function objects, and views are basically lazy algorithms.

Historically, when these adaptors were added, views were required to be copyable, so we required the function objects to be copy_constructible (we couldn't require copyable without ruling out captureful lambdas). The change to make view only require movable came later.

It is probably possible to relax the restriction, but it will need a paper and isn't really high priority.

Goliard answered 9/10, 2021 at 14:20 Comment(6)
"All the algorithms require copy-constructible function objects." Any particular reasons?Expressage
I'm not sure there's a documented rationale. Allowing algorithms to easily call other algorithms freely could be one (allowing move-constructible would require pass-by-reference if the caller still needs the function object afterwards, and the extra level of indirection isn't great if the function object has little or no state).Goliard
Maybe. But since std:: algorithms can accept move-only functions, and if the standard does not explicitly prohibit this, perhaps we can relax the restrictions on ranges:: algorithms.Expressage
"std:: algorithms can accept move-only functions" - citation neededGoliard
Is this allowed? Although it looks fine, I don't know if the standard prohibits this.Expressage
It isn't, but implementations can choose not to check, and if a particular algorithm's implementation happens to not copy then you can get away with it, so to speak.Goliard

© 2022 - 2024 — McMap. All rights reserved.