基于非成员函数的参数进行调度

时间:2011-09-14 10:26:59

标签: c++ polymorphism dispatch

我在(可能是不正确的)假设下,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 个答案:

答案 0 :(得分:3)

重载函数调用通过参数的 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

<强>更新

所以在你的新代码片段中,参数不是类型,它们只是“匿名”;他们没有与之关联的变量名称。

这一切都在编译时解决了。 iterator_traits<T>::iterator_category是一个typedef(通过T模板取决于iterator_traits<T>)。 iterator_traits<T>::iterator_category()正在调用构造函数,以创建该类型的新临时对象,该对象被用作“虚拟”参数。然后,编译器将该调用解析为正确的重载。鉴于此参数是虚拟的,函数内部不需要变量名称。

答案 1 :(得分:2)

是的,这是实现编译时多态的一种方法。所有类型都是编译器已知的,它是如何选择过载的。

  

例如,如果我只编写随机访问的实现   迭代器和输入迭代器,所有具有非随机访问的调用   迭代器将转到接受输入迭代器的函数。这是   一般情况下这种情况是真的还是特殊情况?

只要迭代器标记类是相关的(例如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)。

答案 2 :(得分:2)

您确实使用了编译时多态,它基于对象的静态类型进行调度。

迭代器类别由继承链接(不是迭代器本身),因此:

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

这使得两个foo兼容(注意:如果我们使用了OutputIterator,我们就没有任何东西了,此时会出现编译错误。)

然后我们最终进入重载解析过程的排名部分。由于std::forward_iterator_tagstd::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确实是(动态)a std::random_access_iterator_tag,系统也会将其视为std::forward_iterator_tag,因此将选择与上述相同的重载。