Search code examples
c++pointersc++11for-loopboost-optional

Viewing a raw pointer as a range in range-based for-loop


How can I make a raw pointer behave like a range, for a for-range loop syntax.

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;// will not execute if the pointer is null

Motivation:

It is now vox populi that an boost::optional (future std::optional) value can be viewed as a range and therefore used in a for range loop http://faithandbrave.hateblo.jp/entry/2015/01/29/173613.

When I rewrote my own simplified version of it:

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;
    }
}

Used as

boost::optional<int> opt = 3;
for (int& x : opt) std::cout << x << std::endl;

While looking that code I imagined that it could be generalized to raw (nullable) pointers as well.

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;

instead of the usual if(dptr) std::cout << *dptr << std::endl;. Which is fine but I wanted to achieve the other syntax above.

Attempts

First I tried to make the above Optional version of begin and end work for pointers but I couldn't. So I decided to be explicit in the types and remove all templates:

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;
    }
}

Almost there, it works for

for(double* ptr = std::begin(dptr); ptr != std::end(dptr); ++ptr) 
    std::cout << *ptr << std::endl;

But it doesn't work for the supposedly equivalent for-range loop:

for(double& d : dptr) std::cout << d << std::endl;

Two compilers tell me: error: invalid range expression of type 'double *'; no viable 'begin' function available

What is going on? Is there a compiler magic that forbids the ranged-loop to to work for pointers. Am I making a wrong assumption about the ranged-loop syntax?

Ironically, in the standard there is an overload for std::begin(T(&arr)[N]) and this is very close to it.


Note and a second though

Yes, the idea is silly because, even if possible this would be very confusing:

double* ptr = new double[10];
for(double& d : ptr){...}

would iterate over the first element only. A more clear and also realistic workaround would be to do something like workaround proposed by @Yakk:

for(double& d : boost::make_optional_ref(ptr)){...}

In this way it is clear that we are iterating over one element only and that that element is optional.

Ok, ok, I will go back to if(ptr) ... use *ptr.


Solution

  • Because the way that range-based for works is (from §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 ]

    What are the associated namespaces in this case? (§3.4.2/2, emphasis mine):

    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.

    Thus, there is no place to put your double* begin(double*) such that it will be called by the range-based for statement.

    A workaround for what you want to do is just make a simple wrapper:

    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}) { .. }