【问题标题】:Explain the sample code from cppreference.com about iterator tags解释来自 cppreference.com 的关于迭代器标签的示例代码
【发布时间】:2015-08-17 08:57:57
【问题描述】:

有人可以解释一下 cppreference 网站的示例中的几点吗? 该技术描述了函数重载取决于迭代器类型。 前两个带有“使用”的 typedef 很容易理解。 这些问题与 alg 函数有关:

  1. 在模板参数列表中-“typename = ...”没有参数名称,这是否意味着在函数调用中使用默认值无法覆盖它?
  2. 我是否理解第二个模板参数的使用正确 - 只有在传递的迭代器类型和预期的迭代器标记的类型相等的情况下才会生成函数?

    1. 您能否解释一下在第二个函数 alg 中使用第三个模板参数以及那里的注释:

"typename = void> // 虚拟值以避免模板重新定义错误 "

这段代码在这里(http://en.cppreference.com/w/cpp/iterator/iterator_tags):

template<typename Condition, typename T = void>
using EnableIf_t = typename std::enable_if<Condition::value, T>::type;

template<typename Iterator, typename IteratorTag>
using IsSameIteratorCond = 
    std::is_same<IteratorTag, 
    typename std::iterator_traits<Iterator>::iterator_category>;

template<
    typename BDIter, 
    typename = EnableIf_t<IsSameIteratorCond<BDIter, std::bidirectional_iterator_tag>>>
void alg(BDIter, BDIter)
{
    std::cout << "alg() called for bidirectional iterator\n";
}

template<
    typename RAIter, 
    typename = EnableIf_t<IsSameIteratorCond<RAIter, std::random_access_iterator_tag>>, 
    typename = void> // dummy value to avoid template re-definition error
void alg(RAIter, RAIter)
{
    std::cout << "alg() called for random-access iterator\n";
}

int main()
{
    std::vector<int> v;
    alg(v.begin(), v.end());

    std::list<int> l;
    alg(l.begin(), l.end());
}

【问题讨论】:

    标签: c++ templates template-meta-programming


    【解决方案1】:
    1. typename = ... 声明了一个未命名的模板参数。客户端代码仍然可以覆盖它,但不能在函数定义中使用该参数。此处使用它是因为第二个模板参数用于利用 SFINAE,而不是计算要在定义中使用的类型。

    2. 正确,如果迭代器类型与预期的不同,该函数将从重载候选集中删除。

    3. 需要虚拟参数,因为默认值不是模板签名的一部分,因此alg 的两个版本会尝试定义相同的函数模板。

    使用默认值和虚拟参数对我来说非常难看,我更喜欢使用 tag-dispatch:

    template<typename BDIter>
    void alg(BDIter, BDIter, std::bidirectional_iterator_tag)
    {
        std::cout << "alg() called for bidirectional iterator\n";
    }
    
    template <typename RAIter>
    void alg(RAIter, RAIter, std::random_access_iterator_tag)
    {
        std::cout << "alg() called for random-access iterator\n";
    }
    
    template <typename It>
    void alg(It a, It b)
    {
        return alg(a, b, typename std::iterator_traits<It>::iterator_category{});
    }
    

    【讨论】:

    • 谢谢。不知道“默认值不是模板签名的一部分”,您可以在 C++ 标准或 cppreference 站点中给我一个链接吗?
    • 我找不到简洁的参考,但想想函数声明中的类比;如果函数仅在默认参数上有所不同,那么它们显然是在尝试声明具有相同签名的函数。
    • @amigo421 这里有一些很好的讨论(但不简洁!):stackoverflow.com/questions/5301706/… 这是关于模板默认参数的专家级论文:open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1466.pdf
    【解决方案2】:
    1. 不,用户可以覆盖参数。命名参数 是可选的。如果您不使用它,则不必这样做。
    2. 是的。标签必须等于std::bidirectional_iterator_tag 第一个超载启动,std::random_access_iterator_tag 第二个。
    3. 没有第三个模板参数,两个 函数声明将是相同的,这是非法的。他们 要么必须因参数、返回类型、名称或模板而异 参数(这里就是这种情况)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-01
      • 1970-01-01
      • 2017-04-01
      • 2011-06-25
      • 2010-12-03
      相关资源
      最近更新 更多