Диспетчеризация на основе аргументов функции, не являющейся членом - PullRequest
0 голосов
/ 14 сентября 2011

У меня было (возможно, неверное) предположение, что функции, не являющиеся членами в C ++, не отправляют в зависимости от типа своих аргументов.Но после прочтения о iterator_category кажется, что я могу вызвать функцию в зависимости от типа категории ее аргумента, и этот вызов также обрабатывает наследование.Например, если я напишу только реализации для итератора с произвольным доступом и входного итератора, все вызовы с итератором без произвольного доступа перейдут к функции, которая принимает входной итератор.Это сокращенный пример из книги

template <class T>
foo(T a) {
  foo(a, iterator_traits<T>::iterator_category());
}

template <class T>
foo(T a, random_access_iterator_tag) { \\body}

template <class T>
foo(T a, input_iterator_tag) { \\body}

// presumably this works even for
// ostream_iterator<int> i(cout);
// foo(i);

Является ли этот тип отправки в целом верным или это особый случай?

Должен ли компилятор предупреждать меня, если мои реализации не являются исчерпывающимиНапример, в примере на основе категории итераторов, если я дал реализацию только для итератора с произвольным доступом и двунаправленного итератора, если компилятор пожалуется, что выходной итератор не охватывается.

Это также первый раз, когда я сталкиваюсь сфункция с аргументом, который является только типом, а не объектом / экземпляром.Так можно ли определить функции со встроенными или пользовательскими типами в качестве одного из аргументов, не указав имя экземпляра / объекта этого типа?

Кажется, что для достижения компиляции ниже следует альтернатива CRTPполиморфизм времени.Это правильная интерпретация

template<class T>
int foo(T a) {
  foo(a, a::type());
}

int foo(int a, base) { \\body }
int foo(int a, derived) { \\body }

Ответы [ 3 ]

3 голосов
/ 14 сентября 2011

Перегруженные вызовы функций разрешаются с помощью типов аргументов static (это верно как для функций-членов, так и для функций, не являющихся членами).

Итак:

class Base {};
class Derived : public Base {};

void foo(Base &b) {}
void foo(Derived &d) {}

Base b;
Derived d;
Base &r = d;

foo(b);  // Calls first overload
foo(d);  // Calls second overload
foo(r);  // Calls first overload

UPDATE

Итак, в вашем новом фрагменте кода аргументы не являются типами, они просто "анонимны"; с ними не связано имя переменной.

Это все решается во время компиляции. iterator_traits<T>::iterator_category - это typedef (который будет зависеть от T через шаблон iterator_traits<T>). iterator_traits<T>::iterator_category() вызывает конструктор, чтобы создать новый временный объект этого типа, который используется в качестве «фиктивного» аргумента. Затем компилятор разрешает этот вызов для корректной перегрузки. Учитывая, что этот аргумент является фиктивным, нет необходимости в имени переменной внутри функции.

2 голосов
/ 14 сентября 2011

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

Итератор категории объединен в цепочку наследованием (а не самим итератором), поэтомучто:

InputIterator <- ForwardIterator <- BidirectionalIterator <- RandomAccessIterator

(тоже должно быть OutputIterator, но это не имеет значения)

Используя iterator_traits, вы можете получить категорию итератора, связанную с текущим итератором.Вы создаете фиктивное значение, а затем включается процесс разрешения перегрузки. Предположим для примера, что у вас есть 3 перегрузки:

template <class T>
foo(T a, std::random_access_iterator_tag const&);
  // beware of slicing (in general)

template <class T>
foo(T a, std::forward_iterator_tag const&);

template <class T>
foo(T a, std::input_iterator_tag const&);

Теперь предположим, что я использую foo с итератором списка:

list<int> myList;
foo(myList.begin());

Затем 4 foo находятся в области видимости (разрешение имени).

  • foo(T) немедленно отбрасывается (неверный номер аргумента)
  • foo(T, std::random_access_iterator_tag const&) отбрасывается, потому что нет преобразования из std::bidirectional_iterator_tag в std::random_access_iterator_tag.

Таким образом 2 foo совместимы (примечание: если мы использовали OutputIterator, мыничего бы не осталось, и на этом этапе возникнет ошибка компиляции).

Затем мы наконец перейдем к ранжирующей части процесса разрешения перегрузки.Поскольку std::forward_iterator_tag является «более близким» (более непосредственным) основанием, чем std::input_iterator_tag, поэтому оно имеет более высокий рейтинг.

foo(T, std::forward_iterator_tag const&).


Обратите внимание на статическую частьоб этом, хотя.

std::forward_iterator_tag const& tag = std::vector<int>::iterator_category;
foo(myVector.begin(), tag);

Здесь, хотя tag действительно является (динамическим) std::random_access_iterator_tag, система воспринимается как std::forward_iterator_tag, и, таким образом, та же перегрузка, что и вышебудет выбран.

2 голосов
/ 14 сентября 2011

Да, это способ достижения полиморфизма во время компиляции.Все типы известны компилятору, и именно так он выбирает перегрузку.

Например, если я пишу только реализации для итератора с произвольным доступом и входного итератора, все вызовы с итератором без произвольного доступаперейдем к функции, которая принимает входной итератор.Является ли этот тип отправки в общем случае верным или это особый случай?

Пока классы тегов итератора связаны (например, bidirectional_iterator_tag наследуется от input_iterator_tag).

Должен ли компилятор предупреждать меня, если мои реализации не являются исчерпывающими, например, в примере на основе категории итераторов, если я дал реализацию только для итератора с произвольным доступом и двунаправленного итератора, если компилятор будет жаловатьсяэтот итератор вывода не рассматривается.

Компилятор не знает, выполняет ли ваш код то, что вы хотите, или нет.Однако вы получите сообщение об ошибке, если попытаетесь создать экземпляр функции с неподдерживаемым типом итератора.

Это также первый раз, когда я сталкиваюсь с функцией с аргументом, который является только типом, а необъект / экземпляр.Так можно ли определить функции со встроенными или пользовательскими типами в качестве одного из аргументов?Это должен быть последний аргумент?

Я думаю, что ваш синтаксис просто неверен.Обычно используются объекты (у классов тегов нет членов, поэтому они создаются только для их типа).Полагаю, можно также использовать специализации шаблонов, но тогда они не смогут воспользоваться преимуществами отношений между категориями итераторов (двунаправленный итератор является входным итератором и т. Д.).

Пример того, как обычно выглядит синтаксис.

#include <cstdio>
#include <iterator>
#include <iostream>
#include <vector>
#include <list>

template <class Iter>
void foo_impl(Iter, std::random_access_iterator_tag)
{
    puts("Iter is random access");
}

//for other iterator categories
template <class Iter, class IterCategory>
void foo_impl(Iter, IterCategory)
{
   puts("Iter is other kind of iterator");
}

template <class Iter>
void foo(Iter it)
{
    //use iterator_traits, which will recognize pointers as random access iterators
    foo_impl(it, typename std::iterator_traits<Iter>::iterator_category()); 
}

int main()
{
    int* p = 0;
    std::vector<int>::iterator vec_it;
    std::list<int>::iterator list_it;
    std::ostream_iterator<int> os_it(std::cout);
    foo(p);
    foo(vec_it);
    foo(list_it);
    foo(os_it); 
}

Следующее представляется альтернативой CRTP для достижения полиморфизма во время компиляции.Это правильная интерпретация

Если я не ошибаюсь, даже самое тривиальное использование шаблонов можно рассматривать как полиморфизм во время компиляции.Я также предполагаю, что этот метод старше CRTP (который AFAIK не используется в стандартной библиотеке).

...