Историческое прошлое
Основная причина обсуждается в этом закрытом тикете Boost.
В следующем коде компилятор будет жаловаться, что не найдено начало/конец для «range_2
», который является целочисленным диапазоном. Я предполагаю, что в целочисленном диапазоне отсутствует совместимость с ADL?
#include <vector>
#include <boost/range/iterator_range.hpp>
#include <boost/range/irange.hpp>
int main() {
std::vector<int> v;
auto range_1 = boost::make_iterator_range(v);
auto range_2 = boost::irange(0, 1);
begin(range_1); // found by ADL
end(range_1); // found by ADL
begin(range_2); // not found by ADL
end(range_2); // not found by ADL
return 0;
}
boost::begin()
и boost::end()
не должны быть найдены ADL. На самом деле, Boost.Range специально принимает меры предосторожности, чтобы предотвратить обнаружение boost::begin()
и boost::end()
ADL, объявляя их в namespace boost::range_adl_barrier
, а затем экспортируя их оттуда в namespace boost
. (Этот метод называется «барьером ADL»).
В случае вашего range_1
причина, по которой неквалифицированные вызовы begin()
и end()
работают, заключается в том, что ADL смотрит не только на пространство имен, в котором был объявлен шаблон, но также и на пространства имен, в которых были объявлены аргументы шаблона. В этом случае тип range_1
— boost::iterator_range<std::vector<int>::iterator>
. Аргумент шаблона находится в namespace std
(в большинстве реализаций), поэтому ADL находит std::begin()
и std::end()
(которые, в отличие от boost::begin()
и boost::end()
, не используют барьер ADL для предотвращения их обнаружения ADL).
Чтобы ваш код скомпилировался, просто добавьте "using boost::begin;
" и "using boost::end;
" или явным образом уточните вызовы begin()/end()
с помощью "boost::
".
Расширенный пример кода, иллюстрирующий опасности ADL
Опасность ADL от неквалифицированных вызовов begin
и end
двояка:
- набор связанных пространств имен может быть намного больше, чем можно ожидать. Например. в
begin(x)
, если x
имеет (возможно, по умолчанию!) параметры шаблона или скрытые базовые классы в своей реализации, связанные пространства имен параметров шаблона и его базовых классов также рассматриваются ADL. Каждое из этих связанных пространств имен может привести ко многим перегрузкам begin
и end
во время поиска, зависящего от аргумента.
- неограниченные шаблоны нельзя различить при разрешении перегрузки. Например. в
namespace std
шаблоны функций begin
и end
не перегружаются отдельно для каждого контейнера или иным образом не ограничиваются сигнатурой предоставляемого контейнера. Когда другое пространство имен (такое как boost
) также предоставляет аналогичные неограниченные шаблоны функций, разрешение перегрузки будет учитывать как равное совпадение, так и возникнет ошибка.
Следующие примеры кода иллюстрируют вышеуказанные моменты.
Небольшая библиотека контейнеров
Первым компонентом является наличие шаблона класса контейнера, красиво обернутого в собственное пространство имен, с итератором, производным от std::iterator
, и с универсальными и неограниченными шаблонами функций begin
и end
.
#include <iostream>
#include <iterator>
namespace C {
template<class T, int N>
struct Container
{
T data[N];
using value_type = T;
struct Iterator : public std::iterator<std::forward_iterator_tag, T>
{
T* value;
Iterator(T* v) : value{v} {}
operator T*() { return value; }
auto& operator++() { ++value; return *this; }
};
auto begin() { return Iterator{data}; }
auto end() { return Iterator{data+N}; }
};
template<class Cont>
auto begin(Cont& c) -> decltype(c.begin()) { return c.begin(); }
template<class Cont>
auto end(Cont& c) -> decltype(c.end()) { return c.end(); }
} // C
Небольшая библиотека диапазонов
Второй компонент — библиотека диапазонов, также заключенная в собственное пространство имен, с другим набором неограниченных шаблонов функций begin
и end
.
namespace R {
template<class It>
struct IteratorRange
{
It first, second;
auto begin() { return first; }
auto end() { return second; }
};
template<class It>
auto make_range(It first, It last)
-> IteratorRange<It>
{
return { first, last };
}
template<class Rng>
auto begin(Rng& rng) -> decltype(rng.begin()) { return rng.begin(); }
template<class Rng>
auto end(Rng& rng) -> decltype(rng.end()) { return rng.end(); }
} // R
Неоднозначность разрешения перегрузки через ADL
Проблемы начинаются, когда кто-то пытается превратить диапазон итератора в контейнер, выполняя итерацию с неквалифицированными begin
и end
:
int main()
{
C::Container<int, 4> arr = {{ 1, 2, 3, 4 }};
auto rng = R::make_range(arr.begin(), arr.end());
for (auto it = begin(rng), e = end(rng); it != e; ++it)
std::cout << *it;
}
Живой пример
Поиск имени в зависимости от аргумента для rng
найдет 3 перегрузки для begin
и end
: from namespace R
(потому что rng
живет там), from namespace C
(потому что там живет параметр шаблона rng
Container<int, 4>::Iterator
) и from namespace std
( потому что итератор получен из std::iterator
). При разрешении перегрузки все 3 перегрузки будут считаться равными, что приведет к серьезной ошибке.
Boost решает эту проблему, помещая boost::begin
и boost::end
во внутреннее пространство имен и перетаскивая их во внешнее пространство имен boost
с помощью директив. Альтернативой и, по-моему, более прямым способом будет защита ADL типов (а не функций), поэтому в данном случае шаблоны классов Container
и IteratorRange
.
Живой пример с барьерами ADL
Защиты собственного кода может быть недостаточно
Забавно, но защиты Container
и IteratorRange
с помощью ADL в данном конкретном случае будет достаточно, чтобы приведенный выше код выполнялся без ошибок, потому что std::begin
и std::end
будут вызываться, потому что std::iterator
не защищен с помощью ADL. Это очень удивительно и хрупко. Например. если реализация C::Container::Iterator
больше не является производной от std::iterator
, код перестанет компилироваться. Поэтому предпочтительнее использовать квалифицированные вызовы R::begin
и R::end
в любом диапазоне от namespace R
, чтобы защититься от такого закулисного перехвата имен.
Также обратите внимание, что range-for раньше имел вышеуказанную семантику (выполнение ADL по крайней мере с std
в качестве связанного пространства имен). Это обсуждалось в N3257, что привело к семантическим изменениям в range-for. Текущий диапазон for сначала ищет функции-члены begin
и end
, так что std::begin
и std::end
не будут рассматриваться, независимо от ADL-барьеров и наследования от std::iterator
.
int main()
{
C::Container<int, 4> arr = {{ 1, 2, 3, 4 }};
auto rng = R::make_range(arr.begin(), arr.end());
for (auto e : rng)
std::cout << e;
}
Живой пример
person
TemplateRex
schedule
06.11.2015
find(range, 4)
иfind(begin(v), end(v), 4)
соответственно. Однако это не рекомендуется, если только вы не собираетесь использовать настраиваемый алгоритмfind
в зависимости от переданного ему типа. - person TemplateRex   schedule 06.11.2015any_of_equal
равноany_of
с проверкой предикатов на равенство значению) - person TemplateRex   schedule 06.11.2015max(2, n/2)
правильной границей цикла деления являетсяfloor(sqrt(n)) + 1
. (ошибка заключается в том, чтоany_of
в пустом диапазоне возвращаетtrue
) Также:!any_of == none_of
: см. этот пример а> - person TemplateRex   schedule 06.11.2015any_of
, а не тестирование на простоту :) Тем не менее, спасибо за продуманную исправленную версию! - person sehe   schedule 06.11.2015