【问题标题】:Pass Templated iterator for STL Container为 STL 容器传递模板化迭代器
【发布时间】:2012-08-31 03:44:33
【问题描述】:

对于我的 C++ 类(尚未涵盖 Boost)的练习,我无法编写一个模板化方法来接受两个迭代器来对 STL 容器中的数值求和。
考虑以下示例:

#include <iostream>
#include <iterator>
#include <vector>

template<typename T>
double Sum(const T & c) {
    return 42.0;    // implementation stubbed
}

// need help writing this method signature to accept two iterators
template<typename T>
double Sum(const typename T::const_iterator & begin,
           const typename T::const_iterator & end) {
    return 43.0;    // another implementation stub
}

int main() {
    std::vector<double> v;
    v.push_back(3.14);
    v.push_back(2.71);
    v.push_back(1.61);    // sums to 7.46

    std::cout << Sum(v) << ' '              // line 23
              << Sum(v.begin(), v.end())    // line 24
              << '\n';
}

我希望这段代码能输出42 43,但编译失败。
g++ 给我的错误是:

test_exercise2.cpp: In function ‘int main()’:
test_exercise2.cpp:24: error: no matching function for call to ‘Sum(__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >, __gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >)’

如果我注释掉第 24 行,我会得到 42 作为输出,正如预期的那样。
无论是否存在第二个模板化方法,我都会收到相同的错误消息,因此由于某种原因,它无法将第 24 行的调用解析为我编写的第二个方法。

对于接受两个迭代器的方法,我必须有什么签名?


我之所以坚持这一点是因为我需要支持对std::map&lt;K, V&gt; 的第二个元素求和。这将需要另外两个重载来调用 -&gt;second 而不是取消引用迭代器:
1.template&lt;typename K, typename V&gt; double Sum(const std::map&lt;K, V&gt; &amp; m);(这个没问题)
2. 另一个涉及地图上的迭代器。

如果我能弄清楚如何为std::liststd::map 指定迭代器的传递,我觉得我将能够为std::map 编写方法。我可以接受使用模板模板的解决方案。


编辑:问题的准确措辞(省略非贡献性句子)。
“上一个练习”中的容器是std::vector&lt;double&gt;std::list&lt;double&gt;std::map&lt;std::string, double&gt;

创建一个名为 Sum() 的模板函数,它接受模板 参数 T 作为输入并返回一个双精度值。模板参数将 成为一个容器。

  • 在实现中获取一个迭代器 (T::const_iterator) 用于结束。然后创建一个循环迭代容器 T 并添加所有 价值观。最后返回总和。
  • 在主程序中,为与上一个练习不同的容器调用 Sum() 函数。

创建的 Sum() 函数计算完整的总和 容器。还要创建一个计算总和的 Sum() 函数 两个迭代器之间。然后该函数使用模板参数 用于迭代器类型并接受两个迭代器,开始和结束 迭代器。

【问题讨论】:

  • 您是否需要为采用两个迭代器的Sum 重载提供特化?您是否被告知 SFINAE 是什么? concrete 类型的地图是否需要专业化? (提示:你不能有函数模板的部分特化)
  • 是的,作业指定了需要两个迭代器的Sum 重载。该课程没有教授 SFINAE 是什么,但我很高兴了解它。
  • SFINAE 是 Substitution Failure Is Not An Error 的首字母缩写词,这意味着如果编译器开始将模板视为重载决议的候选者,但在执行类型推断时,将类型替换为模板失败(无法替换),模板将被丢弃,但这不会导致编译错误。在更广泛的意义上,该术语用于指代使用语言的该功能通过强制替换错误来启用/禁用模板的技术。
  • 回到你的问题,你应该重新审视需求。我不太确定你认为他们在问什么和他们真正在问什么是一回事。对于初学者来说,模板函数不能部分特化(只能完全特化,即你可以特化std::map&lt;int,double&gt;,但你不能特化std::map&lt;T,U&gt;,其中TU 是未绑定的类型。我并不是说它不能完成,只是如果没有解释 SFINAE,解决方案可能超出了课程的范围。
  • 哦,真的吗?嗯,知道这会很有用。由于其他原因,我实际上认为这是一个糟糕的问题,所以我感谢你指出我应该阅读的内容,所以我会去做。在不久的将来,我可能只发布一个与此作业有关的问题。

标签: c++ templates stl iterator


【解决方案1】:

你把这件事复杂化了。你想要一对任何类型的迭代器吗?好吧,这就像 .. 两个任意类型的参数一样简单。

template<typename Iterator>
double Sum(const Iterator& begin,
           const Iterator& end) {
    return 43.0;    // another implementation stub
}

问题解决了。

顺便说一下,从 C++ 标准库中得到一个提示:如果您不能取消引用迭代器,请让用户提供一个函数来从迭代器中获取值。不要特例std::map,因为明天有std::unordered_map,后天有boost::multimap,还有各种各样的乐趣。如果我想让你对 std::map 中的 keys 求和,而不是对值求和呢?

您的硬编码案例有点复杂。一对必须来自std::map的迭代器?如果没有明确的模板参数,甚至不确定是否可能。

template<typename K, typename V, typename Comp, typename Alloc>
double Sum(
    const std::map<K, V, Comp, Alloc>& map
) { ... }

请注意,我特别指出它必须是 std::map 实例化。这允许编译器推断参数。从这里,您可以访问迭代器。

【讨论】:

  • 我同意你的向量和列表解决方案(因为这是我最初的解决方案)。我进一步同意用户应该提供一个谓词来获取更复杂的迭代器的值。不幸的是...
  • 我想你一个字。或者可能是一个句子。
  • ... 令人遗憾的是,作业指定我们为std::map 硬编码一个特殊情况来对这些值求和。我有点想从纯粹的蔑视中获得分数。所以在这一点上,这对我来说真的只是一个好奇心。
  • map 的尝试将不起作用,因为该类型处于不可推断的上下文中。
  • 是的,我意识到了这一点。短暂的大脑放屁,但我改变了它,现在参数在可推断的上下文中。
【解决方案2】:

正如 DeadMG 所说,简单的方法是在迭代器的类型上进行模板化。另一方面,常见的约定是按值传递迭代器:

template <typename Iterator>
double Sum( Iterator begin, Iterator end );

至于原代码为什么不行,问题是容器的类型不可推导:

template <typename T>
double Sum( T::const_iterator begin, T::const_iterator end );
Sum( v.begin(), v.end() );        // [*] Assume v == const std::vector<double>&

当编译器试图推断Sum 的参数类型时,它只会看到v.begin()v.end() 返回的类型,它们是迭代器。根据该类型,它无法猜测容器的类型。为了能够确定T 是什么类型,它必须测试所有非模板类型,并生成模板类型的所有无限可能实例,以查看它们是否具有与@987654328 类型匹配的嵌套类型const_iterator @ 和 v.end()。因为那是不可能的,所以语言一开始就禁止它。

除此之外,与注释[*] 相关,即使类型是可推导出的,重载决议也会在函数的参数上执行,而不是表达式稍后如何使用。在您的程序中,.begin() 的参数是 std::vector&lt;double&gt; 非 const lvalue。因为不是 const,所以选择的重载会产生一个非常量的迭代器(即使在你要调用的函数中,也不需要读取)。

【讨论】:

    【解决方案3】:

    对比迭代器时的显着特征,例如std::list 与来自std::map 的迭代器是后者有一对类型作为它们的value_type。也就是说,给定std::map&lt;K, V&gt;,那么std::map&lt;K, V&gt;::value_typestd::iterator_traits&lt;std::map&lt;K, V&gt;::iterator&gt;::value_type都是std::pair&lt;const K, V&gt;

    因此我建议您的 Sum 模板接受任何迭代器,但它不对迭代器给出的元素(即*it)进行操作,而是在“视图”上操作:element(*it)。现在您可以注意确保element 在面对一对时“做正确的事”。

    作为提示,您可以将Sum 声明为以下内容(通过一些元编程来正确获取返回类型):

    namespace result_of {
    
    // Second template parameter is an implementation detail
    template<
        typename Iterator
        , typename ValueType = typename std::iterator_traits<Iterator>::value_type
    >
    struct Sum {
        // general case: sum over the value type directly
        typedef ValueType type;
    };
    
    // If an iterator admits an std::pair as its value_type then we end up here
    template<typename Iterator, typename Key, typename Value>
    struct Sum<Iterator, std::pair<Key, Value> > {
        // special case: sum over the second type of the value
        typedef Value type;
    };
    
    } // result_of
    
    template<typename Iterator>
    typename result_of::Sum<Iterator>::type Sum(Iterator begin, Iterator end);
    

    【讨论】:

    • +1 所有容器和迭代器都有value_type。映射的value_type 将是std::pair&lt;const K, T&gt;,但包含一对常量对象和任何其他类型的向量的值映射也是如此。话虽如此,您的答案是我必须得到的最好的,但我担心要求没有很好地说明。
    • @DavidRodríguez-dribeas 如果有一系列配对的人要求其中一个Sum,那么最糟糕的情况就是他们会得到那个总和:) 更严重的是,Sum已经不限于仅在对value_type 求和时才有效(即使在非对情况下),因此无论如何已经存在先决条件。不妨问一下,对第二种类型求和也有意义。把这些变成约束留给读者作为练习。
    • +1,很酷的元编程。我肯定会对此有所了解并尝试一下。感谢您扩展我的思维!
    猜你喜欢
    • 2014-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 1970-01-01
    • 2021-01-09
    • 2012-07-11
    • 1970-01-01
    相关资源
    最近更新 更多