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

发布于 2024-12-05 02:05:30 字数 920 浏览 0 评论 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 }

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

若水般的淡然安静女子 2024-12-12 02:05:30

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

所以:

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

Overloaded function calls are resolved via the static types of the arguments (this is true both for member and non-member functions).

So:

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

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 on T via the iterator_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.

平安喜乐 2024-12-12 02:05:30

您确实正在使用编译时多态性,它根据对象的静态类型进行分派。

迭代器类别通过继承(而不是迭代器本身)链接起来,因此:(

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 < code>foo 在范围内找到(名称解析)。

  • foo(T) 立即被丢弃(参数数量错误)
  • foo(T, std::random_access_iterator_tag const&) 被丢弃,因为没有从 std 进行转换::bidirection_iterator_tagstd::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 ,因此将选择与上面相同的重载。

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:

InputIterator <- ForwardIterator <- BidirectionalIterator <- RandomAccessIterator

(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:

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&);

Now suppose that I use foo with a list iterator:

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

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 from std::bidirectional_iterator_tag to std::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 than std::input_iterator_tag, it is therefore ranked higher.

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


Note the static portion of this though.

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

Here, even though the tag really is (dynamic) a std::random_access_iterator_tag, it is seen by the system as a std::forward_iterator_tag, and thus the same overload that above will be selected.

行至春深 2024-12-12 02:05:30

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

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

只要迭代器标签类是相关的(例如,bi Direction_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 更老(据我所知,标准库中没有使用 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.

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. Is this
kind of dispatch generally true or is this a special case ?

As long as the iterator tag classes are related (e.g bidirectional_iterator_tag is inherited from input_iterator_tag).

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.

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.

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
? Does this have to be last argument ?

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.

#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); 
}

The following seems to be an alternative to CRTP to achieve compile
time polymorphism. Is that a correct interpretation

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文