英文:
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
的构造函数中按值接受x
、y
和z
,即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<ranges::view Vx, ranges::view Vy, ranges::view Vz>
struct Processor
{
public:
Processor(Vx&& x, Vy&& y, Vz&& 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<int>
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<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 {
// ...
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论