问题
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
.
回答1:
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 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 ]
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) — IfT
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}) { .. }
回答2:
It is a useful lie to think that for(:)
loops are implemented by "calling std::begin
and std::end
in a ADL-activated context". But that is a lie.
The standard instead basically does a parallel implementation of the std::begin
and std::end
in itself. This prevents the language's low level constructs from depending on its own library, which seems like a good idea.
The only lookup for begin
by the language is the ADL-based lookup. Your pointer's std::begin
won't be found, unless you are a pointer to something in std
. The std::begin( T(&)[N} )
isn't found this way by the compiler, but instead that iteration is hard-coded by the language.
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 {};
}
}
now you can:
void foo(double * d) {
for(double& x : boost::as_optional(d)) {
std::cout << x << "\n";
}
without having to repeat the type double
.
Note that an rvalue optional
to a non-reference returns a T const*
, while an rvalue optonal
to a T&
returns a T*
. Iterating over a temporary in a writing context is probably an error.
来源:https://stackoverflow.com/questions/28242073/viewing-a-raw-pointer-as-a-range-in-range-based-for-loop