问题描述
我想为我的 API 提供一些迭代器,这些迭代器会自动调用所需的成员函数来直接迭代返回的值。更准确地将以下结构体和主函数视为 MVE:
struct Shape
{
double getArea()
{
return ....//calculate area;
}
double getVolume()
{
return ....//calculate volume;
}
std:pair<double,double> getVolumeAndArea()
{
return std:make_pair...;
}
// some data members
}
int main(){
std:vector<Shape> shapes;
//fill shapes
for(const auto area : area(shapes))
// do something with area
for(const auto volume : volume(shapes))
// do something with volume
for(const auto [volume,area] : volumeAndArea(shapes))
// do something with volume and area
}
显然,我可以迭代形状并直接在代码中编写它,或者在提供的 Lamba 内部调用发生的地方编写一些 std:for_each
。
因此,我的问题是如何用最少的样板来解决这个问题。我应该使用一些 boost
迭代器还是继承一些 std::iterator
作为这些免费函数的返回值?我希望有一种我已经监督过的简单方法。由于提到的选项似乎很冗长。拥有这个相当通用的东西也会很好。因为在不重复迭代器样板的情况下将它用于多个类会很好。
此外,我不限于旧的 c++ 版本,因此,也欢迎使用不错的 c++20 解决方案。
因此,有人可以向我指出如何以现代方式执行此操作的好示例或最佳实践建议吗?我有信心自己锻炼细节,因为我不想要求完成的解决方案。
解决方法
在 C++20 中,算法的范围版本可以对成员进行投影,这几乎可以让您获得所需的易用性,但语法略有不同
std::ranges::for_each(shapes,[](const auto & area) {
// do something with area
},&Shape::getArea);
// ^_____________^ projection on member
这是一个demo
,这是一个可能的实现,它使用迭代器而不是任何范围(特别是对于区域情况):
class area {
private:
class iterator {
public:
// needed so that it acts like a std::iterator
using difference_type = int;
using value_type = double;
using reference = double;
using pointer = std::add_pointer<value_type>;
using iterator_category = std::forward_iterator_tag;
double operator*() const {
// compute the area when accessing the value of the iterator
return shapes_[index_].getArea();
}
iterator &operator++() {
index_++;
return *this;
}
bool operator==(const iterator &rhs) const { return index_ == rhs.index_; }
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
bool operator<(const iterator &rhs) const { return index_ < rhs.index_; }
iterator(size_t index,const std::vector<Shape>& shapes) : index_(index),shapes_(shapes) {}
private:
size_t index_;
const std::vector<Shape>& shapes_;
};
public:
area(const std::vector<Shape>& shapes) : shapes_(shapes) {}
iterator begin() const { return iterator{0,shapes_}; }
iterator end() const{ return iterator{shapes_.size(),shapes_}; }
private:
const std::vector<Shape>& shapes_;
};
...
int main() {
std::vector<Shape> shapes;
for (const double area : area(shapes)) {
}
}
范围看起来容易多了!
就其价值而言,您可能可以在此处提取 iterator
类并将其重用于所有不同的函数(例如传递一个 std::function
对每个形状调用)。
我想我是通过上面给出的有用提示弄明白的。谢谢 cigien、mattlangford 和 mooning duck。
使用 std::ranges::transform_view
和投影为编写以下内容提供了很好且富有表现力的选择
using std::ranges::transform_view;
for(const auto area : transform_view(shapes,&Shape::getArea ))
std::cout << area << "\n";
另见https://godbolt.org/z/3xqn5bdWE。
还提供一种类似 API 的解决方案,可以将其包装为:
auto area(std::vector<Shape>& shapes)
{
using std::ranges::transform_view;
return transform_view(shapes,&Shape::getArea);
}
然后可以用作
for(const auto area : area(shapes))
//do something with area
由于原始的 transform_view 版本也非常具有表现力,我认为 area(...) 解决方案更难理解。