3

对于 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)值视为一个范围,因此可以将其用于范围循环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版本beginend为指针工作,但我做不到。所以我决定明确类型并删除所有模板:

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

4

3 回答 3

7

因为基于范围的工作方式是(来自§6.5.4):

begin-exprend-expr确定如下
- 如果_RangeT是数组类型,[..]
- 如果_RangeT是类类型,[..]
- 否则,begin-exprend-expr分别是begin(__range)and end(__range),其中beginend在关联的命名空间 (3.4.2) 中查找。[注:不执行普通的非限定查找(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}) { .. }
于 2015-01-30T18:31:01.440 回答
3

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

std::begin相反,该标准基本上对andstd::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非引用的右值返回 a T const*,而 a 的右值optonal返回T&a T*。在写作上下文中迭代临时可能是一个错误。

于 2015-01-30T18:44:59.307 回答
0

TL;博士

此构造可用于范围 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 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] 指针一起使用。查看以获取更多信息。

于 2020-10-04T19:48:55.080 回答