Просмотр необработанного указателя в виде диапазона в цикле для цикла
Как заставить 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
.
Ответы
Ответ 1
Потому что способ, основанный на диапазоне для работ (из § 6.5.5):
begin-expr и end-expr определяются следующим образом: - если _RangeT
- тип массива, [..]
- если _RangeT
- тип класса, [..]
- в противном случае begin-expr и end-expr равны begin(__range)
и end(__range)
соответственно, где begin
и end
просматриваются в соответствующих пространствах имен (3.4.2). [Примечание: обычный неквалифицированный поиск (3.4.1) не выполняется. -end note]
Каковы связанные пространства имен в этом случае? (§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}) { .. }
Ответ 2
Полезно полагать, что циклы for(:)
реализуются путем вызова std::begin
и std::end
в контексте, активированном ADL ". Но это ложь.
Стандарт вместо этого в основном выполняет параллельную реализацию std::begin
и std::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
.
Обратите внимание, что rvalue optional
для не ссылки ссылается на T const*
, а rvalue optonal
на T&
возвращает a T*
. Итерация по временному в контексте записи, вероятно, является ошибкой.