Вот контейнер:
namespace container_namespace
{
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class container
{
// stuff
class iterator
{
// stuff
};
};
}
Где в вышесказанном я определяю advance(InputIt &, Distance N)
для того, чтобы иметь возможность использовать advance()
в моем main()
через ADL (поиск, зависящий от аргумента):
int main(int argc, char **argv)
{
using namespace std;
using namespace container_namespace;
container<int> c;
// Add elements to c here
container<int>::iterator it = c.begin();
advance(it, 20);
}
И есть обычай advance()
функция выбрана вместо std::advance
?
Я видел примеры обычая advance()
функция, определенная внутри класса итератора, и примеры, где она была определена внутри пространства имен, а внутри класса итератора объявлена только дружба. Как правильно использовать ADL? Другие примеры SO были неясны по этому вопросу.
При поиске по неквалифицированному имени будет учитываться то, что найдено при обычном поиске (в вашем случае, шаблон функции std::advance
) и что находит ADL (в вашем случае advance(iterator&, Distance N)
, Они будут рассматриваться по разрешению перегрузки на равных основаниях.
Ваша цель состоит в том, чтобы убедиться, что ваш пользовательский аванс лучше соответствует, и самый простой способ сделать это — убедиться, что это не шаблонная функция: шаблоны проиграть не шаблонам, если они одинаково хороши. Если твой iterator
шаблон класса (или, как показано, член шаблона класса), вы можете сделать advance
не шаблонный друг, определенный внутри определения шаблона класса.
Я считаю, что самый безопасный способ — это определить friend
из container
или же iterator
, Определенная таким образом функция помещается в namespace container_namespace
так это можно найти по ADL:
namespace container_namespace {
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class container {
//...
template <typename Diff>
friend void advance(iterator&, Diff) {
//...
}
};
}
Другим вариантом может быть определение его непосредственно в namespace container_namespace
, Таким образом, вы можете иметь общую реализацию для всех ваших контейнеров и / или реализовать диспетчеризацию тегов для обработки разных категорий итераторов, как это делается в std::advance
реализация:
namespace container_namespace {
template <typename Iter, typename Diff>
void advance(Iter&, Diff) {
std::cout << "ADL-ed advance\n";
}
}
Проблема с этим подходом состоит в том, что он может вызвать двусмысленность, когда std::advance
находится в области видимости (спасибо, @TC):
DEMO
Обратите внимание, что вы не можете определить advance
следующее:
namespace container_namespace {
template <typename element_type, typename element_allocator_type, typename Diff>
void advance(typename container<element_type, element_allocator_type>::iterator&, Diff) {
std::cout << "ADL-ed advance\n";
}
}
потому что тип его первого аргумента потерпит неудачу (см. Не выведенные контексты).
Хотя оба опубликованных ответа верны (и я проголосовал за оба), я подумал, что расскажу об этом чуть подробнее, для тех, кто найдет это в будущем.
Значения друзей
Для начала «друг» имеет другое значение для функций в классе. Если это просто объявление функции, то оно объявляет данную функцию другом класса и предоставляет доступ к ее закрытым / защищенным членам. Однако, если это реализация функции, это означает, что функция (а) является другом класса, (б) не является членом класса и (в) недоступна из какого-либо окружающего пространства имен. то есть. это становится глобальной функцией, которая доступна только через зависимый от аргумента поиск (ADL).
Возьмите следующий тестовый код, например:
#include <iostream>
#include <iterator>
namespace nsp
{
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
element_type numbers[50];
friend class iterator;
public:
class iterator : public std::iterator<std::bidirectional_iterator_tag, element_type>
{
private:
element_type *i;
template <class distance_type>
friend void advance(iterator &it, distance_type n);
friend typename std::iterator_traits<iterator>::difference_type distance(const iterator &first, const iterator &last)
{
return last.i - first.i;
}public:
iterator(element_type &_i)
{
i = &(_i);
}
element_type & operator *()
{
return *i;
}
element_type & operator = (const iterator source)
{
i = source.i;
return *this;
}
bool operator != (const iterator rh)
{
return i != rh.i;
}
iterator & operator ++()
{
++i;
return *this;
}
iterator & operator --()
{
--i;
return *this;
}
};iterator begin()
{
return iterator(numbers[0]);
}iterator end()
{
return iterator(numbers[50]);
}template <class distance_type>
friend void advance(iterator &it, distance_type n)
{
it.i += 2 * n;
}
};}int main(int argc, char **argv)
{
nsp::test_container<int> stuff;
int counter = 0;
for (nsp::test_container<int>::iterator it = stuff.begin(); it != stuff.end(); ++it)
{
*it = counter++;
}
nsp::test_container<int>::iterator it = stuff.begin(), it2 = stuff.begin();
using namespace std;
cout << *it << endl;
++it;
cout << *it << endl;
advance(it, 2);
cout << *it << endl;
std::advance(it, 2);
cout << *it << endl;
int distance_between = distance(it2, it);
cout << distance_between << endl;
cin.get();
return 0;
}
Если изнутри main()
, advance()
ADL будет функционировать, и будет вызван пользовательский аванс для итератора класса. Однако если nsp::advance()
, nsp::test_container<int>::advance()
или же stuff.advance()
если это будет сделано, это приведет к ошибкам компиляции («нет соответствующего вызова функции»).
Проблемы с шаблоном
Хотя верно, что перегрузки не шаблонных функций будут вызываться в предпочтении перегрузок шаблонных функций, это не имеет значения для использования ADL. Независимо от того, является ли функция шаблонной или не шаблонной, будет вызвана правильная перегрузка для определенного типа. К тому же, advance()
в частности, требуется параметр шаблона с типом расстояния (int, long int, long long int и т. д.), это невозможно пропустить, потому что мы не знаем, какой тип компилятор будет выводить, скажем, «1000000», и мы не знаем, к каким типам может прибегнуть программист advance()
, К счастью, нам не нужно беспокоиться о частичной специализации, так как std::advance()
находится в другом пространстве имен для нашего пользовательского продвижения, и может просто реализовать наш собственный advance()
с нашим жестко закодированным типом итератора, как показано в примере выше.
Это все еще работает, если наш итератор сам по себе является шаблоном и принимает параметры — мы просто включаем параметры в предварительный шаблон и таким образом жестко кодируем тип итератора. например.:
template <class element_type, class distance_type>
friend void advance(iterator<element_type>, distance_type distance);
Больше проблемы с шаблоном (примечание)
Хотя это не относится конкретно к реализации advance()
это относится к реализации функций друзей класса в целом. Вы заметите, что в приведенном выше примере я реализовал не шаблонную функцию distance()
непосредственно внутри класса итератора, в то время как advance()
Функция template’d объявлена как друг вне класса итератора, но внутри класса test_container. Это должно проиллюстрировать точку.
У вас не может быть реализована функция не шаблонного друга вне класса, с которым она дружит, если класс является шаблоном (или частью шаблона), так как ваш компилятор выдаст ошибку. Однако шаблон будет работать advance()
Можно быть объявленным вне класса только с определением, включенным в класс друга. advance()
Функция также может быть реализована в классе друга, я просто выбрал не для того, чтобы проиллюстрировать этот момент.
Затенение параметров функции друга шаблона
Это не относится к приведенному выше примеру, но может быть ловушкой для программистов, использующих шаблонные функции друзей. Если у вас есть шаблонный класс и функция друга, которая работает с этим классом, очевидно, что вам нужно будет указать параметры шаблона в определении функции, а также в классе. Например:
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
element_type numbers[50];
public:
template <class element_type, class element_allocator_type>
friend void do_stuff(test_container<element_type, element_allocator_type> &container)
{
numbers[1] = 5; // or whatever
}
};
Однако вышеприведенное не будет работать, потому что компилятор считает, что вы используете одинаковые имена для element_type и element_allocator_type как переопределение параметров шаблона, впервые использованных в определении test_container, и выдаст ошибку. Поэтому вы должны использовать разные имена для них. то есть. это работает:
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
element_type numbers[50];
public:
template <class c_element_type, class c_element_allocator_type>
friend void do_stuff(test_container<c_element_type, c_element_allocator_type> &container)
{
numbers[1] = 5; // or whatever
}
};
Это все-
Я надеюсь, что любой, кто столкнется с этим, получит некоторую пользу — большая часть этой информации каким-то образом или по форме распределяется по стеку, но объединение ее важно для новичка.
Вам нужно две вещи, чтобы воспользоваться ADL:
Первая вещь проста, но вторая требует немного заботы. Вот то, что вы должны окончательно не делать:
template<typename Element, typename Allocator>
struct vector {
struct iterator {};
};
// trouble ahead!
template<typename Element, typename Allocator>
void advance(typename vector<Element, Allocator>::iterator it, int n)
{
…
}
В этой конкретной форме, как выясняется, параметры шаблона Element
а также Allocator
в advance
являются без выводима. Другими словами, advance
вызывается только в том случае, если вызывающий передает эти параметры, например ns::advance<int, std::allocator<int>>(it, n)
, С призывами к advance
обычно не похоже, что это довольно ужасный кандидат, и мы можем прямо исключить это.
Короткое и приятное решение — определить встроенную функцию друга внутри iterator
, Что важно в этом методе, так это то, что он определяет не шаблон функции, а функцию. vector<E, A>::iterator
не шаблон класса, но сам по себе класс, один на vector<E, A>
специализация.
template<typename Element, typename Allocator>
struct vector {
struct iterator {
friend void advance(iterator it, int n)
{ … }
};
};
advance
находится в ADL, поскольку это правильное пространство имен, и поскольку это не шаблонная функция, она предпочтительнее std::advance
, Все хорошо на земле, не так ли? Ну, есть ограничение в том, что вы не можете взять адрес ns::advance
на самом деле вы не можете назвать это вообще.
Обычно вы можете положить вещи вернуться к нормальной жизни добавив декларацию пространства имен … за исключением того, что мы не можем напрямую в нашем случае, потому что vector
это шаблон класса. На самом деле вы сталкиваетесь со многими подводными камнями, когда впервые начинаете изучать шаблоны классов и друзей, например. вы можете увидеть этот разумный вопрос FAQ и попытайтесь воспользоваться этим, только чтобы обнаружить, что это не применимо в данной ситуации.
Если вы действительно заботитесь о именах пользователей advance
за пределами неквалифицированных звонков (например, чтобы взять адрес или что у вас есть), мой совет «распутать» iterator
от vector
:
// might now need additional parameters for vector to fill in
template<typename Element>
struct vector_iterator;
template<typename Element, typename Allocator>
struct vector {
using iterator = vector_iterator<Element>;
…
};
В частности, если мы последуем совету предыдущего пункта FAQ, мы можем получить что-то вроде формы:
template<typename Element>
void advance(vector_iterator<Element> it, int n);
Стоит отметить, что это, очевидно, шаблон функции, и он будет предпочтительнее, например, std::advance
из-за правила частичного заказа. Частичное упорядочение — почти всегда мой предпочтительный подход к этому вопросу.