Как я могу сделать необработанный указатель вести себя как диапазон, для синтаксиса цикла для диапазона.
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
,
Потому что способ, основанный на диапазоне для работ (из §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}) { .. }
Полезно думать, что 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*
, Перебор временного в контексте записи, вероятно, является ошибкой.