在基于范围的 for 循环中将原始指针视为范围
Viewing a raw pointer as a range in range-based for-loop
范围循环语法,如何使原始指针的行为类似于范围。
double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;// will not execute if the pointer is null
赋予动机:
现在,boost::optional
(未来std::optional
)值可以被视为一个范围,因此用于范围循环 http://faithandbrave.hateblo.jp/entry/2015/01/29/173613。
当我重写自己的简化版本时:
namespace boost {
template <class Optional>
decltype(auto) begin(Optional& opt) noexcept{
return opt?&*opt:nullptr;
}
template <class Optional>
decltype(auto) end(Optional& opt) noexcept{
return opt?std::next(&*opt):nullptr;
}
}
用作
boost::optional<int> opt = 3;
for (int& x : opt) std::cout << x << std::endl;
在查看该代码时,我想象它也可以推广到原始(可为空)指针。
double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;
而不是通常的if(dptr) std::cout << *dptr << std::endl;
.这很好,但我想实现上面的其他语法。
尝试
首先,我尝试使上述Optional
版本的begin
和end
用于指针,但我不能。所以我决定在类型中明确并删除所有模板:
namespace std{ // excuse me, this for experimenting only, the namespace can be removed but the effect is the same.
double* begin(double* opt){
return opt?&*opt:nullptr;
}
double* end(double* opt){
return opt?std::next(&*opt):nullptr;
}
}
几乎在那里,它适用于
for(double* ptr = std::begin(dptr); ptr != std::end(dptr); ++ptr)
std::cout << *ptr << std::endl;
但它不适用于所谓的等效范围循环:
for(double& d : dptr) std::cout << d << std::endl;
两个编译器告诉我:error: invalid range expression of type 'double *'; no viable 'begin' function available
这是怎么回事?是否有编译器魔法禁止范围循环用于指针。我对范围循环语法是否做出了错误的假设?
具有讽刺意味的是,在标准中,std::begin(T(&arr)[N])
存在过载,这非常接近它。
注意和一秒钟
是的,这个想法很愚蠢,因为即使可能,这也非常令人困惑:
double* ptr = new double[10];
for(double& d : ptr){...}
将仅迭代第一个元素。一个更清晰且更现实的解决方法是执行类似于@Yakk提出的解决方法:
for(double& d : boost::make_optional_ref(ptr)){...}
通过这种方式,很明显我们只迭代一个元素,并且该元素是可选的。
好的,好的,我会回去if(ptr) ... use *ptr
.
因为基于范围的作品的方式是(从 §6.5.4 开始):
开始-EXPR 和结束-EXPR 确定
如下 — 如果_RangeT
是数组类型,[..]
— 如果_RangeT
是类类型,[..]
— 否则,开始-expr 和结束-expr 分别是begin(__range)
和end(__range)
,其中begin
并在关联的命名空间 (3.4.2) 中查找end
。[ 注意:普通非限定查找 (3.4.1) 不执行。—尾注 ]
在这种情况下,关联的命名空间是什么?(§3.4.2/2,强调我的):
命名空间和类集按以下方式确定:
(2.1) — 如果T
是基本类型,则其关联的命名空间和类集均为空。
因此,没有地方可以放置您的double* begin(double*)
,以便它将被基于范围的for
语句调用。
您要执行的操作的解决方法是制作一个简单的包装器:
template <typename T>
struct PtrWrapper {
T* p;
T* begin() const { return p; }
T* end() const { return p ? p+1 : nullptr; }
};
for (double& d : PtrWrapper<double>{dptr}) { .. }
for(:)
循环是通过"在 ADL 激活的上下文中调用std::begin
和std::end
"来实现的,这是一个有用的谎言。 但那是谎言。
相反,该标准基本上是并行实现std::begin
和std::end
本身。 这可以防止语言的低级结构依赖于它自己的库,这似乎是一个好主意。
语言对begin
的唯一查找是基于 ADL 的查找。 不会找到指针的std::begin
,除非您是指向 std
中的某些内容的指针。 编译器不会以这种方式找到std::begin( T(&)[N} )
,而是通过语言硬编码迭代。
namespace boost {
template<class T>
T* begin( optional<T>&o ) {
return o?std::addressof(*o):nullptr;
}
template<class T>
T* begin( optional<T&>&&o ) {
return o?std::addressof(*o):nullptr;
}
template<class T>
T const* begin( optional<T> const&o ) {
return o?std::addressof(*o):nullptr;
}
template<class T>
T* end( optional<T>&o ) {
return o?std::next(begin(o)):nullptr;
}
template<class T>
T* end( optional<T&>&&o ) {
return o?std::next(begin(o)):nullptr;
}
template<class T>
T const* end( optional<T> const&o ) {
return o?std::next(begin(o)):nullptr;
}
template<class T>
boost::optional<T&> as_optional( T* t ) {
if (t) return *t;
return {};
}
}
现在,您可以:
void foo(double * d) {
for(double& x : boost::as_optional(d)) {
std::cout << x << "n";
}
无需重复类型 double
.
注意,optional
到非引用的右值返回T const*
,而T&
optonal
的右值返回T*
。 在写入上下文中迭代临时可能是一个错误。
TL;DR
此构造可用于 for 循环范围:
std::views::counted(raw_ptr, !!raw_ptr)
详
C++20 提供了多种使用范围库创建临时可迭代对象的方法。例如:
#include <ranges>
#include <iostream>
int main()
{
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *raw_ptr = a;
for(int i : std::views::counted(raw_ptr, 10))
std::cout << i << ' ';
std::cout << 'n';
for(int i : std::views::counted(raw_ptr, 1))
std::cout << i << ' ';
std::cout << 'n';
std::cout << "empty for null pointer pointern";
raw_ptr = nullptr;
for(int i : std::views::counted(raw_ptr, 0))
std::cout << i << ' ';
std::cout << 'n';
std::cout << "Exitn";
}
指纹
1 2 3 4 5 6 7 8 9 10
1
empty for null pointer
Exit
类似地,std::views::subrange
可以与(开始、结束)指针一起使用。查看库以获取更多信息。
- 为什么在全局范围内使用"extern int a"似乎不行?
- 尝试通过多个向量访问变量时,向量下标超出范围
- 错误:未在此范围内声明'reverse'
- 正在将指针转换为范围
- 使用std::transform将一个范围的元素添加到另一个范围中
- 在基于范围的for循环中使用结构化绑定声明
- 如何计算数据类型的范围,例如int
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 将浮动的heightmap数组导出为16位原始值
- 在C++中查找范围的长度
- 给定一个向量,如何找到该向量的所有子集和的原始索引
- 如何设置一个范围来提取我想要获得的信息
- 并行用于C++17中数组索引范围内的循环
- 为左值和右值的包装器实现C++范围
- 有没有办法从非C/C++文件中读取C++原始字符串文字的内容
- 求出有多少个数字是完美平方,而sqrt()是L,R范围内的素数
- 从原始指针(衰减的 C 样式数组)和大小生成范围::视图
- 如果存储原始参考的类不在范围之外,可以捕获会员参考安全吗?
- 未在此范围内声明原始字符串“R”
- 在基于范围的 for 循环中将原始指针视为范围