0

我处于(可能不正确的)假设下,即 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 }
4

3 回答 3

3

重载的函数调用通过参数的静态类型来解决(对于成员函数和非成员函数都是如此)。

所以:

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()正在调用构造函数,以创建该类型的新临时对象,该对象用作“虚拟”参数。然后编译器将该调用解析为正确的重载。鉴于此参数是一个虚拟参数,因此函数内部不需要变量名。

于 2011-09-14T10:29:58.000 回答
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)更老。

于 2011-09-14T10:58:30.107 回答
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());

然后,foo在范围内找到 4 个(名称解析)。

  • foo(T)立即丢弃(参数数量错误)
  • foo(T, std::random_access_iterator_tag const&)被丢弃,因为没有从std::bidirectional_iterator_tag到的转换std::random_access_iterator_tag

这使得 2foo都兼容(注意:如果我们使用了 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真的是(动态) a std::random_access_iterator_tag,系统也会将其视为 a std::forward_iterator_tag,因此将选择与上述相同的重载。

于 2011-09-14T11:58:44.047 回答