Как заставить raw-указатель вести себя как диапазон для синтаксиса цикла 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
Мотивация:
Теперь vox populi указывает, что значение boost::optional
(future std::optional
) может рассматриваться как диапазон и поэтому используется в цикле диапазона for 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
версию begin
и end
для указателей, но я не смог. Поэтому я решил быть явным в типах и удалять все шаблоны:
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
Что происходит? Есть ли магия компилятора, которая запрещает работать с указателем. Я принимаю неверное предположение о синтаксисе Ranged-loop?
По иронии судьбы, в стандарте есть перегрузка для 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
.