基于非成员函数的参数进行调度
我的假设是(可能不正确)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 }
I was under the (possibly incorrect) assumption that non-member functions in C++ do not dispatch based on the type of its arguments. But after reading about iterator_category
it seems I can call a function depending on the category type of its argument and the call also handles inheritance. For instance if I write only the implementations for random access iterator and input iterator, all calls with a non-random access iterator will go to the function that accepts input iterator. This is a shortened example from the book
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);
Is this kind of dispatch generally true or is this a special case ?
Is the compiler supposed to warn me if my implementations are not exhaustive, for example in the iterator category based example, if I gave an implementation for random access iterator and bidirectional iterator only, should the compiler complain that output iterator is not covered.
This is also the first time I encountered a function with an argument that is only a type, and not an object/instance. So can I define functions with built-in or user-defined types as one of its arguments without specifying the name of the instance/object of that type ?
The following seems to be an alternative to CRTP to achieve compile time polymorphism. Is that a correct interpretation
template<class T>
int foo(T a) {
foo(a, a::type());
}
int foo(int a, base) { \\body }
int foo(int a, derived) { \\body }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
重载函数调用通过参数的静态类型来解析(对于成员函数和非成员函数都是如此)。
所以:
更新
所以在你的新代码片段中,参数不是类型,它们只是“匿名”;它们没有与之关联的变量名称。
这一切都在编译时解决。::iterator_category() 正在调用构造函数,以创建该类型的新临时对象,该对象用作“虚拟”参数。然后,编译器将该调用解析为正确的重载。鉴于此参数是一个虚拟参数,因此函数内不需要变量名。
iterator_traits::iterator_category
是一个 typedef(它将通过iterator_traits
模板依赖于T
)。 iterator_traitsOverloaded function calls are resolved via the static types of the arguments (this is true both for member and non-member functions).
So:
UPDATE
So in your new code snippet, the arguments are not types, they're just "anonymous"; they don't have a variable name associated with them.
This is all resolved at compile-time.
iterator_traits<T>::iterator_category
is a typedef (which will depend onT
via theiterator_traits<T>
template).iterator_traits<T>::iterator_category()
is calling the constructor, to create a new temporary object of that type, which is being used as a "dummy" argument. The compiler then resolves that call to the correct overload. Given that this argument is a dummy, there's no need for a variable name inside the function.您确实正在使用编译时多态性,它根据对象的静态类型进行分派。
迭代器类别通过继承(而不是迭代器本身)链接起来,因此:(
也应该是一个
OutputIterator
,但这里并不重要)使用
iterator_traits
,您可以检索与当前迭代器关联的迭代器类别。您创建一个虚拟值,然后开始重载解析过程。为了举例,假设您有 3 个重载:现在假设我将
foo
与列表迭代器一起使用:然后, 4 < code>foo 在范围内找到(名称解析)。
foo(T)
立即被丢弃(参数数量错误)foo(T, std::random_access_iterator_tag const&)
被丢弃,因为没有从std 进行转换::bidirection_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&)
。但请注意其中的静态部分。
在这里,尽管
tag
实际上是(动态)std::random_access_iterator_tag
,但系统将其视为std::forward_iterator_tag
,因此将选择与上面相同的重载。You are indeed using compile-time polymorphism, which dispatches based on the static type of the object.
The iterator categories are chained by inheritance (not the iterator themselves), so that:
(should be an
OutputIterator
too, but it does not matter here)Using
iterator_traits
, you can retrieve the iterator category associated with the current iterator. You create a dummy value, and then the overload resolution process kicks in. Suppose for the sake of example that you have 3 overloads:Now suppose that I use
foo
with a list iterator:Then, the 4
foo
are found in the scope (name resolution).foo(T)
is immediately discarded (wrong number of argument)foo(T, std::random_access_iterator_tag const&)
is discarded because there is no conversion fromstd::bidirectional_iterator_tag
tostd::random_access_iterator_tag
.This leaves 2
foo
both being compatible (note: if we had used an OutputIterator, we would not have anything left, and a compilation error would be raised at this point).We then finally get into the ranking part of the overload resolution process. Since
std::forward_iterator_tag
is a "closer" (more immediate) base thanstd::input_iterator_tag
, it is therefore ranked higher.foo(T, std::forward_iterator_tag const&)
is selected.Note the static portion of this though.
Here, even though the
tag
really is (dynamic) astd::random_access_iterator_tag
, it is seen by the system as astd::forward_iterator_tag
, and thus the same overload that above will be selected.是的,这是一种实现编译时多态性的方法。所有类型都是编译器已知的,这就是它选择重载的方式。
只要迭代器标签类是相关的(例如,
bi Direction_iterator_tag
继承自input_iterator_tag
)。编译器不知道您的代码是否执行您想要的操作。但是,如果您尝试使用不受支持的迭代器类型实例化该函数,则会收到错误消息。
我认为你的语法不正确。通常使用对象(但标记类没有任何成员,因此仅为其类型创建它们)。我想,也可以使用模板专业化,但是这样就无法利用迭代器类别之间的关系(双向迭代器是输入迭代器等)。
语法通常看起来如何的示例。
如果我没记错的话,即使是最琐碎的模板使用也可以被认为是编译时多态性。我还猜测这种技术比 CRTP 更老(据我所知,标准库中没有使用 CRTP)。
Yes, this is a way to achieve compile-time polymorphism. All the types are known to the compiler and that's how it selects the overload.
As long as the iterator tag classes are related (e.g
bidirectional_iterator_tag
is inherited frominput_iterator_tag
).The compiler doesn't know if your code does what you want or not. However, you will get an error if you try to instantiate the function with an unsupported iterator type.
I think your syntax is just incorrect. Objects are generally used (the tag classes don't have any members, though, so they are only created for their type). I suppose, template specializations could also be used, but then those wouldn't be able to take advantage of the relationships between iterator categories (bidirectional iterator is an input iterator etc).
A sample how the syntax normally looks.
If I'm not mistaken, even the most trivial use of templates can be thought of as compile-time polymorphism. I also guess this technique is older than CRTP (which AFAIK is not used in the standard library).