【问题标题】:Dispatch based on arguments of a non-member function基于非成员函数的参数调度
【发布时间】:2011-09-14 10:26:59
【问题描述】:

我的假设是(可能不正确)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 }

【问题讨论】:

  • 在你进入模板和迭代器之前,你应该先看看函数覆盖。基于参数类型具有覆盖“调度”的所有函数。
  • @selalerer:你的意思是“过载”,而不是“覆盖”。

标签: c++ polymorphism dispatch


【解决方案1】:

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

所以:

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

更新

所以在你的新代码 sn-p 中,参数不是类型,它们只是“匿名的”;它们没有关联的变量名。

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

【讨论】:

  • 是的,我也是这么想的,但是当一个只提供随机 _access_iterator_tag 和 input_iterator_tag 的实现时,如何将所有 non_random_access 迭代器的调用路由到带有 input_iterator_tag 标记的实现。我会想,如果签名中没有完全匹配的函数,调用将不会通过,或者是否正在调用隐式类型提升规则。我没有尝试过,只是阅读 STL Tutotrial and Reference Guide 一书中的示例
  • 感谢您提供有关未使用对象的信息。即使签名不完全匹配,我仍然对如何调用正确的函数感到困惑。或者它是否正确调用它?我会假设与 ostream_iterator 的调用会失败。除非在计算函数调用与函数体的正确匹配时考虑继承,即使在编译时也是如此。
  • @san:一般来说,编译器在解决竞争重载时会尝试找到“最佳匹配”。请记住,在您的示例中,它不是基于迭代器类型解析,而是基于iterator_traits&lt;T&gt;::iterator_category 解析。
【解决方案2】:

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

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

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&amp;) 被丢弃,因为没有从 std::bidirectional_iterator_tagstd::random_access_iterator_tag 的转换。

这使得 2 foo 两者都兼容(注意:如果我们使用了 OutputIterator,我们将不会留下任何东西,此时会引发编译错误)。

然后我们最终进入重载解决过程的排名部分。由于std::forward_iterator_tag 是比std::input_iterator_tag“更接近”(更直接)的基数,因此它的排名更高。

foo(T, std::forward_iterator_tag const&amp;) 被选中。


请注意这里的静态部分。

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,因此将选择与上述相同的重载。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • 感谢您的回答。我同时编辑了问题以使其更清晰并消除语法错误。您的示例对我来说很有意义,我感到困惑的是,如果没有发生完全匹配,则使用从 input_iterator 派生的迭代器的调用将调用输入迭代器的实现。所以我认为我的问题实际上是关于重载是否也考虑到继承。对 C++ 仍然很陌生(才几个月),所以在一些基础知识上还很不稳定。
    • 详细说明,在您的示例中,有一个全部实现。是不是即使没有这样的包罗万象,并且已经实现了基本情况,如果没有特殊情况匹配,基本情况也会被调用。
    猜你喜欢
    • 2014-01-04
    • 1970-01-01
    • 1970-01-01
    • 2020-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-29
    相关资源
    最近更新 更多