首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在基于范围的for-循环中将原始指针视为范围。

在基于范围的for-循环中将原始指针视为范围。
EN

Stack Overflow用户
提问于 2015-01-30 18:10:41
回答 3查看 2.5K关注 0票数 3

对于for-range循环语法,如何使原始指针的行为像范围一样?

代码语言:javascript
复制
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中使用。

当我重写我自己的简化版本时:

代码语言:javascript
复制
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;
    }
}

用作

代码语言:javascript
复制
boost::optional<int> opt = 3;
for (int& x : opt) std::cout << x << std::endl;

在查看该代码时,我设想它也可以泛化为原始(可空)指针。

代码语言:javascript
复制
double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;

而不是通常的if(dptr) std::cout << *dptr << std::endl;。这很好,但是我想实现上面的另一个语法。

尝试

首先,我试图让上面的Optional版本的beginend为指针工作,但我做不到。因此,我决定在类型上保持显式,并删除所有模板:

代码语言:javascript
复制
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;
    }
}

就快到了,这很管用

代码语言:javascript
复制
for(double* ptr = std::begin(dptr); ptr != std::end(dptr); ++ptr) 
    std::cout << *ptr << std::endl;

但它不适用于所谓的等价物范围循环:

代码语言:javascript
复制
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])存在过载,这非常接近它。

Note和第二个尽管是

是的,这个想法很愚蠢,因为即使可能的话,这也会让人非常困惑:

代码语言:javascript
复制
double* ptr = new double[10];
for(double& d : ptr){...}

只对第一个元素进行迭代。一个更清晰、也更现实的解决办法是做一些类似@Yakk提出的解决方案:

代码语言:javascript
复制
for(double& d : boost::make_optional_ref(ptr)){...}

通过这种方式,很明显,我们只对一个元素进行迭代,并且该元素是可选的。

好的,好的,我会回到if(ptr) ... use *ptr

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-01-30 18:31:01

因为基于范围的工作方式是(参见第6.5.4节):

开始-支出和结束-费用的确定如下 -如果_RangeT是数组类型, -如果_RangeT是类类型,.. -否则,where expr和end-expr分别为begin(__range)end(__range),其中beginend在关联的命名空间中查找(3.4.2)。[注:不执行普通不合格查找(3.4.1)。-end注记]

在这种情况下,相关的命名空间是什么?(第3.4.2/2节,重点是我):

命名空间和类的集合以下列方式确定: (2.1) -如果T是一种基本类型,那么它所关联的名称空间和类集都是--都是空的

因此,没有地方可以放置您的double* begin(double*),以至于它将被基于范围的for语句调用。

你想要做的事情的解决办法就是做一个简单的包装:

代码语言:javascript
复制
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}) { .. }
票数 7
EN

Stack Overflow用户

发布于 2015-01-30 18:44:59

认为for(:)循环是通过“在ADL激活的上下文中调用std::beginstd::end”来实现的,这是一个有用的谎言。但那是个谎言。

相反,标准基本上是对std::beginstd::end本身进行并行实现。这防止了语言的低级别结构依赖于它自己的库,这似乎是个好主意。

该语言对begin的唯一查找是基于ADL的查找。您的指针的std::begin不会被找到,除非您是指向std中某些内容的指针。编译器不是以这种方式找到std::begin( T(&)[N} )的,而是该语言对迭代进行了硬编码。

代码语言:javascript
复制
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 {};
  }
}

现在你可以:

代码语言:javascript
复制
void foo(double * d) {
  for(double& x : boost::as_optional(d)) {
    std::cout << x << "\n";
}

而不必重复double类型。

注意,非引用的rvalue optional返回T const*,而T&的rvalue optonal返回T*。迭代写入上下文中的临时内容可能是一个错误。

票数 3
EN

Stack Overflow用户

发布于 2020-10-04 19:48:55

TL;DR

此结构可用于范围for循环:

代码语言:javascript
复制
std::views::counted(raw_ptr, !!raw_ptr)

详细信息

C++20提供了大量使用ranges库创建临时迭代的方法。对于示例

代码语言:javascript
复制
#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";
}

打印

代码语言:javascript
复制
1 2 3 4 5 6 7 8 9 10 
1

empty for null pointer

Exit

类似地,std::views::subrange可以与(start,end)指针一起使用。有关更多信息,请查看图书馆

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28242073

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档