【问题标题】:How to constraint a template to be iterable ranges using concepts?如何使用概念将模板限制为可迭代范围?
【发布时间】:2021-12-16 16:38:48
【问题描述】:

假设我有一些模板函数,它返回传递给它的一些可迭代对象的中值。

类似:

template<typename T>
decltype(auto) find_median_sorted(T begin)
{
  // some code here
}

现在我想确保我将 T 限制为始终可迭代。我正在尝试学习如何在 C++ 中使用concepts,那么有什么方法可以在这里使用concept 来确保T 是可迭代的吗?

我确信还有其他方法可以检查它是否是可迭代的,但这是 concepts 的错误用例吗? 我也遇到过这个与元素可迭代相关的帖子here,但我不确定这如何适用于我的案例。

【问题讨论】:

  • 有几个不同的iterator concepts,你可能会选择最广泛的一个,它仍然允许你编写你想到的算法。也许random_access_iterator
  • @NathanPierson 是的,谢谢你,我可以使用一些迭代器概念。但是,如果我使用 random_acces_iterator 的话,我对我的语法会是什么样子感到有点困惑。函数签名是什么样的?
  • 函数的约束通常与其实现密切相关。您指的是什么可迭代对象?它是一个迭代器吗?或者它是一个可以用迭代器迭代的范围?另外len的类型不应该是int,不是泛型的。
  • 仅供参考 declspec(auto) 似乎在这里可疑。我希望中位数计算函数返回一个常规的auto,因为我不知道你什么时候会想在这里返回一个引用。

标签: c++ templates c++20 c++-concepts function-templates


【解决方案1】:

我正在尝试学习如何在 C++ 中使用概念,那么有什么方法可以在这里使用concept 来确保 T 是可迭代的吗?

您可以在此处从&lt;ranges&gt; 标头中获得标准概念std::ranges::range。这样,您的函数将如下所示:

#include <ranges>  // std::ranges::range

template<std::ranges::range T>
decltype(auto) find_median_sorted(T const& container) {
    // some code here
}

要将范围限制为仅用于随机访问可迭代范围,您可以从&lt;ranges&gt; 标头再次使用std::ranges::random_access_range

#include <ranges>  // std::ranges::random_access_range

template<std::ranges::random_access_range T>
decltype(auto) find_median_sorted(T const& container) {
    // some code here
}

或通过迭代器概念std::random_access_iterator 如下:

#include <iterator> // std::random_access_iterator 

template<typename  T>
decltype(auto) find_median_sorted(T const& container)
requires std::random_access_iterator<std::ranges::iterator_t<T>>
{
    // some code here
}

【讨论】:

  • 传递范围和长度对我来说似乎很奇怪。在调用站点创建子范围/视图微不足道时,为什么还要麻烦?
  • 首先,你的函数按值传递,这意味着它将整个向量复制到你的函数中。另外,你应该使用random_access_range来约束T而不是random_access_iterator
  • @Frank 感谢您提出这个问题。你是对的,如果我使用范围是多余的,会删除它。
  • decltype(conatiner.begin()) 不能应用于原始数组。你应该使用iterator_t&lt;T&gt;
【解决方案2】:

编辑:问题中的原始代码看起来更像迭代器,因此为什么要使用基于迭代器的答案。就目前而言,基于范围的答案更适合 OP 的要求。

“老式”迭代器与其现代解释之间的一个重要区别是哨兵(也称为结束迭代器)不必与开始迭代器的类型相同。

因此,您应该使用 2 个单独的约束。一个用于迭代器本身,另一个用于哨兵,使用std::sentinel_for

至于迭代器本身,您将希望使用最松散的迭代器概念,它允许您执行您想做的工作。如果可能的话,最好是std::input_iterator

应用约束只是用适当的概念替换模板中的typename

#include <iterator>

template<std::input_iterator I, std::sentinel_for<I> S>
auto find_median_sorted(I begin, S end){
  // some code here
}

【讨论】:

  • 感谢您的回答!但这比在模板中使用 std::ranges::range 更好吗?这是否使代码更“通用”并使其适应更广泛的对象?
  • @beep_boop 在功能上是等价的。根据函数中发生的事情以及函数的使用方式,其中一种可能会导致代码更清晰。你的问题是关于如何做受约束的迭代器,所以我觉得这样的答案确实是合适的。
  • 具体来说,你总是可以从一个范围中获取一对迭代器,你可以从一个迭代器和它的哨兵中创建一个范围,所以任何一个都可以工作。
  • 这里只有约束std::input_iterator太松了。
  • 最严格?几乎所有的迭代器都是input_iterator
猜你喜欢
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 2016-08-01
  • 2022-06-26
  • 1970-01-01
  • 2014-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多