Просмотр необработанного указателя как диапазона в цикле for на основе диапазона

Как я могу сделать необработанный указатель вести себя как диапазон, для синтаксиса цикла для диапазона.

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 (будущее 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;
}
}

Используется в качестве

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(double& d : dptr) std::cout << d << std::endl;

Два компилятора говорят мне: error: invalid range expression of type 'double *'; no viable 'begin' function available

Что здесь происходит? Есть ли магия компилятора, которая запрещает дальний цикл работать для указателей. Я делаю неправильное предположение о синтаксисе дальнего цикла?

Как ни странно, в стандарте есть перегрузка для 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,

3

Решение

Потому что способ, основанный на диапазоне для работ (из §6.5.4):

начать-выр а также конец выраж определяются следующим образом
— если _RangeT тип массива, [..] — если _RangeT это тип класса, [..] — иначе, начать-выр а также конец выраж являются begin(__range) а также end(__range)соответственно где begin
а также end ищутся в связанных пространствах имен (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}) { .. }
7

Другие решения

Полезно думать, что 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,

Обратите внимание, что значение optional без ссылки возвращает T const*в то время как значение optonal к T& возвращает T*, Перебор временного в контексте записи, вероятно, является ошибкой.

3

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector