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

alf*_*lfC 3 c++ pointers for-loop c++11 boost-optional

对于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
Run Code Online (Sandbox Code Playgroud)

动机:

现在,vox populi可以将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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用作

boost::optional<int> opt = 3;
for (int& x : opt) std::cout << x << std::endl;
Run Code Online (Sandbox Code Playgroud)

在查看代码时,我想象它也可以推广到原始(可空)指针.

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;
Run Code Online (Sandbox Code Playgroud)

而不是通常的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

几乎在那里,它适用于

for(double* ptr = std::begin(dptr); ptr != std::end(dptr); ++ptr) 
    std::cout << *ptr << std::endl;
Run Code Online (Sandbox Code Playgroud)

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

for(double& d : dptr) std::cout << d << std::endl;
Run Code Online (Sandbox Code Playgroud)

两个编译器告诉我: error: invalid range expression of type 'double *'; no viable 'begin' function available

到底是怎么回事?是否存在禁止ranged-loop为指针工作的编译器魔法.我是否对远程循环语法做出了错误的假设?

具有讽刺意味的是,在标准中有一个过载std::begin(T(&arr)[N]),这是非常接近它.


注意和第二个虽然

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

double* ptr = new double[10];
for(double& d : ptr){...}
Run Code Online (Sandbox Code Playgroud)

将仅迭代第一个元素.一个更清晰,更现实的解决方法是做一些像@Yakk提出的解决方法:

for(double& d : boost::make_optional_ref(ptr)){...}
Run Code Online (Sandbox Code Playgroud)

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

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

Bar*_*rry 7

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

begin-exprend-expr确定如下
- 如果_RangeT是数组类型,[..]
- 如果_RangeT是类类型,[..]
- 否则,begin-exprend-expr分别是begin(__range)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}) { .. }
Run Code Online (Sandbox Code Playgroud)

  • 或者,非标准版本:在思考`ADD上下文中的'std :: begin`和`std :: end`被`for(:)`循环使用时,会得到你的大部分真相,标准反而差不多没有引用它们或使用它们的重新实现.这主要是因为低级语言结构不依赖于库. (3认同)