将一个 std::ranges::view 类型的对象传递给一个类的正确方法是什么?

huangapple go评论61阅读模式
英文:

What is the correct approach to passing a std::ranges::view type object to a class?

问题

这个问题涉及将一个std::ranges::view类型对象作为类的成员变量。我正在尝试更好地理解它们,所以可能会写一些糟糕的代码!

假设我们有一个名为foo()的函数,该函数与某个数据源进行交互,并返回一个std::tuple,其中包含范围视图:

auto foo(int i)
{
  auto v1 = views::iota(i) | views::transform([](int i){ return i++; });
  auto v2 = views::iota(i) | views::filter([](int i){ return i % 2; });
  auto v3 = views::iota(i) | views::transform([](int i){ return i += 3; });
  return std::make_tuple(v1, v2, v3);
}

然后,我们需要对foo()的三个返回视图进行一些处理,它们之间存在一些相互依赖。因此,我想将它们存储为类的成员,但我想避免复制底层数据的全部内容,所以我会这样做:

template<typename Vx, typename Vy, typename Vz>
struct Processor
{
public:
    Processor(Vx&& x, Vy&& y, Vz&& z) : m_x{x}, m_y{y}, m_z{z} {}

    auto bar() { /* 使用m_x、m_y和m_z进行工作 */ }

private:
    Vx m_x;
    Vy m_y;
    Vz m_z;
};

在这种情况下,我将不得不使用std::move来初始化Processor的实例,像这样:

auto [x, y, z] = foo(10);
Processor p(std::move(x), std::move(y), std::move(z));

或者,我可以在Processor的构造函数中按值接受xyz,即Processor(Vx x, Vy y, Vz z) : m_x{x}, m_y{y}, m_z{z} {},然后可以这样构建实例:

auto [x, y, z] = foo(10);
Processor p(x, y, z);

所以,我只想澄清一下:(a) 是否这是一个好主意,(b) 两种方法中应该采用哪种?我怀疑按值传递是正确的(值语义,对吗?)因为我们没有复制底层数据,只是每个视图。

最后,是否有办法进一步约束Processor的模板参数,例如,如果我在将它们传递给构造函数时知道每个范围的底层类型,是否可以编写类似ranges::view<int>的内容?

谢谢!godbolt链接:https://godbolt.org/z/Gx419949n

英文:

this question concerns having a std::ranges::view type object as a member variable of a class. I'm trying to better understand them so I may write some poor code along the way here!

Suppose we have a function foo() which interfaces with the some data source and returns a std::tuple of range views:

auto foo(int i)
{
  auto v1 = views::iota(i) | views::transform([](int i){ return i++; });
  auto v2 = views::iota(i) | views::filter([](int i){ return i % 2; });
  auto v3 = views::iota(i) | views::transform([](int i){ return i += 3; });
  return std::make_tuple(v1,v2,v3);
}

and we then need to do some processing which involves the three return views of foo(), with there being some interdependence between them. Therefore, I'd like to store them as members of a class, but i'd like to avoid copying the entirety of the underlying data, so I do something like:

template&lt;ranges::view Vx, ranges::view Vy, ranges::view Vz&gt;
struct Processor
{
public:
    Processor(Vx&amp;&amp; x, Vy&amp;&amp; y, Vz&amp;&amp; z) : m_x{x}, m_y{y}, m_z{z} {}

    auto bar() { /* work using m_x, m_y and m_z */ }

private:
    Vx m_x;
    Vy m_y;
    Vz m_z;
};

in which case i'd have to initialise an instance of Processor using std::move, like so:

auto [x,y,x] = foo(10);
Processor p(std::move(x), std::move(y), std::move(z));

Or alternatively, I take x,y and z by value in the constructor of Processor i.e. Processor(Vx x, Vy y, Vz z) : m_x{x}, m_y{y}, m_z{z} {} and can then construct an instance as:

auto [x,y,x] = foo(10);
Processor p(x, y, z);

So, i'd just like to clarify (a) whether this is a good idea and (b) which approach should i be taking of the two? I suspect that pass-by-value is correct (value-semantics, right?) since we are not copying the underlying data, only the view of each one.

Finally, is there any way to constrain the template parameters of Processor further, for example writing something like ranges::view&lt;int&gt; if I know the underlying type of each range when I pass it to the constructor?

Thank you! godbolt link: https://godbolt.org/z/Gx419949n

答案1

得分: 1

Views are generally passed by value since usually it doesn't own the data, it just keeps a pointer to the data (of course there are a few exceptions such as single_view), so Processor(Vx x, Vy y, Vz z) should be preferred.

However, it's important to note that views are only guaranteed to be movable. For example, the standard owning_view is a move-only view, so you should ensure that the value is moved to the member in the member initializer list:

template<ranges::view Vx, ranges::view Vy, ranges::view Vz>
struct Processor
{
public:
  Processor(Vx x, Vy y, Vz z) 
    : m_x{std::move(x)}, m_y{std::move(y)}, m_z{std::move(z)} {}
private:
  Vx m_x;
  Vy m_y;
  Vz m_z;
}

Finally, is there any way to constrain the template parameters of Processor further, for example writing something like ranges::view<int> if I know the underlying type of each range when I pass it to the constructor?

You can use the requires clause for additional constraint checking:

template<ranges::view Vx, ranges::view Vy, ranges::view Vz>
  requires std::same_as<std::ranges::range_value_t<Vx>, int> &&
           std::same_as<std::ranges::range_value_t<Vy>, int> &&
           std::same_as<std::ranges::range_value_t<Vz>, int>
struct Processor {
  // ...
};
英文:

Views are generally passed by value since usually it doesn't own the data, it just keeps a pointer to the data (of course there are a few exceptions such as single_view), so Processor(Vx x, Vy y, Vz z) should be preferred.

However, it's important to note that views are only guaranteed to be movable. For example, the standard owning_view is a move-only view, so you should ensure that the value is moved to the member in the member initializer list

template&lt;ranges::view Vx, ranges::view Vy, ranges::view Vz&gt;
struct Processor
{
public:
  Processor(Vx x, Vy y, Vz z) 
    : m_x{std::move(x)}, m_y{std::move(y)}, m_z{std::move(z)} {}
private:
  Vx m_x;
  Vy m_y;
  Vz m_z;
}

> Finally, is there any way to constrain the template parameters of
> Processor further, for example writing something like
> ranges::view<int> if I know the underlying type of each range when I
> pass it to the constructor?

You can use the requires clause for additional constraint checking

template&lt;ranges::view Vx, ranges::view Vy, ranges::view Vz&gt;
  requires std::same_as&lt;std::ranges::range_value_t&lt;Vx&gt;, int&gt; &amp;&amp; 
           std::same_as&lt;std::ranges::range_value_t&lt;Vy&gt;, int&gt; &amp;&amp; 
           std::same_as&lt;std::ranges::range_value_t&lt;Vz&gt;, int&gt;
struct Processor {
  // ...
};

huangapple
  • 本文由 发表于 2023年2月24日 02:21:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75548823.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定