将原始指针视为基于范围的 for 循环中的范围
Viewing a raw pointer as a range in range-based for-loop
对于 for-range 循环语法,如何使原始指针表现得像一个范围。
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
)值可以被视为一个范围,因此可以在 for range 循环中使用 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;
。这很好,但我想实现上面的其他语法。
尝试次数
首先,我尝试使 begin
和 end
的上述 Optional
版本适用于指针,但我做不到。所以我决定在类型中明确并删除所有模板:
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-range 循环:
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):
begin-expr and end-expr are determined as follows
— if _RangeT
is an array type, [..]
— if _RangeT
is a class type, [..]
— otherwise, begin-expr and end-expr are begin(__range)
and end(__range)
, respectively, where begin
and end
are looked up in the associated namespaces (3.4.2). [ Note: Ordinary unqualified lookup (3.4.1)
is not performed. —end note ]
在这种情况下关联的命名空间是什么? (§3.4.2/2,强调我的):
The sets of namespaces and classes are determined in the following way:
(2.1) — If T
is a fundamental type, its associated sets of namespaces and classes are both empty.
因此,没有地方可以放置您的 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(:)
循环是由 "calling std::begin
and std::end
in a ADL-activated context" 实现的是一个有用的谎言。但那是谎言。
该标准本身基本上是 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
到一个非引用returns一个T const*
,而一个右值optonal
到一个T&
returns一个T*
。在写入上下文中迭代临时对象可能是错误的。
TL;DR
此构造可用于循环范围内:
std::views::counted(raw_ptr, !!raw_ptr)
详情
C++20 提供了大量创建 ad-hoc 可迭代对象的方法,使用 范围库 。对于 example:
#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 pointer\n";
raw_ptr = nullptr;
for(int i : std::views::counted(raw_ptr, 0))
std::cout << i << ' ';
std::cout << '\n';
std::cout << "Exit\n";
}
版画
1 2 3 4 5 6 7 8 9 10
1
empty for null pointer
Exit
同样,std::views::subrange
可以与 (start, end] 指针一起使用。查看 library 了解更多信息。
对于 for-range 循环语法,如何使原始指针表现得像一个范围。
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
)值可以被视为一个范围,因此可以在 for range 循环中使用 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;
。这很好,但我想实现上面的其他语法。
尝试次数
首先,我尝试使 begin
和 end
的上述 Optional
版本适用于指针,但我做不到。所以我决定在类型中明确并删除所有模板:
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-range 循环:
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):
begin-expr and end-expr are determined as follows
— if_RangeT
is an array type, [..]
— if_RangeT
is a class type, [..]
— otherwise, begin-expr and end-expr arebegin(__range)
andend(__range)
, respectively, wherebegin
andend
are looked up in the associated namespaces (3.4.2). [ Note: Ordinary unqualified lookup (3.4.1) is not performed. —end note ]
在这种情况下关联的命名空间是什么? (§3.4.2/2,强调我的):
The sets of namespaces and classes are determined in the following way:
(2.1) — IfT
is a fundamental type, its associated sets of namespaces and classes are both empty.
因此,没有地方可以放置您的 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(:)
循环是由 "calling std::begin
and std::end
in a ADL-activated context" 实现的是一个有用的谎言。但那是谎言。
该标准本身基本上是 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
到一个非引用returns一个T const*
,而一个右值optonal
到一个T&
returns一个T*
。在写入上下文中迭代临时对象可能是错误的。
TL;DR
此构造可用于循环范围内:
std::views::counted(raw_ptr, !!raw_ptr)
详情
C++20 提供了大量创建 ad-hoc 可迭代对象的方法,使用 范围库 。对于 example:
#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 pointer\n";
raw_ptr = nullptr;
for(int i : std::views::counted(raw_ptr, 0))
std::cout << i << ' ';
std::cout << '\n';
std::cout << "Exit\n";
}
版画
1 2 3 4 5 6 7 8 9 10
1
empty for null pointer
Exit
同样,std::views::subrange
可以与 (start, end] 指针一起使用。查看 library 了解更多信息。