【问题标题】:How do I specify a parameter type by some quality of the parameter rather than its exact type?如何通过参数的某些质量而不是其确切类型来指定参数类型?
【发布时间】:2020-11-14 17:08:47
【问题描述】:

所以我在 C++ 中有一个函数如下:

void print_vec(std::vector<int> v) {
  for (int i: v) {
    std::cout << i << ' ';
  }
  std::cout << std::endl;
}

这很有效,但仅适用于 int 向量。我想要类似的东西

void print_iter(Iterable<CanDoCout> iterable) {
  for (auto i: iterable) {
    std::cout << i << ' ';
  }
  std::cout << std::endl;
}

如何实现print_iter?或者我怎样才能知道如何实现这样的泛型函数?

【问题讨论】:

  • 请不要在您的问题中添加“已解决”。接受答案是表明您的问题已解决的方式。
  • @SamVarshavchik 真的,已经有涵盖 C++20 概念的教科书了?这将是令人惊讶的。

标签: c++ typing


【解决方案1】:

你要找的是templates:

template <class T>
void print_iter(const T& iterable) {
  for (const auto& i: iterable) {
    std::cout << i << ' ';
  }
  std::cout << std::endl;
}

您可以更进一步,使用conceptsSFINAE 将您的模板限制为仅接受您应用的操作的类型。

【讨论】:

  • 谢谢你,这行得通!是否可以将模板的类型限制为仅支持 cout 的类型(例如在 Haskell 中,您可以将类型限制为 Show),还是应该让它抛出错误?
  • 你可以。带有概念或 SFINAE。请参阅 HolyBlackCat 的回答。
  • “或者我应该让它抛出一个错误” 在概念之前,这是更可取的(它只会抛出一个错误!只是......一个更好的错误。 ..).
  • 在循环中首选const auto&amp; 以避免在此处复制。
  • @AsteroidsWithWings 是的,你
【解决方案2】:

假设您可以访问 C++20,这就是您使用概念限制模板接受的类型的方式:

template <typename T>
concept printable = requires(T t)
{
    std::cout << t;
};

template <std::ranges::range T>
requires printable<std::ranges::range_value_t<T>>
void print_iter(const T &iterable)
{
    for (const auto &it : iterable)
        std::cout << it << ' ';
    std::cout << '\n';
}

concept printable = ...printable 定义为一个概念((通常)一种类型的一组要求)。当表达式std::cout &lt;&lt; t 是良构的,假设tT 类型的变量,这个概念就得到了满足。

print_iter()的定义中:

  • template &lt;std::ranges::range T&gt;template &lt;typename T&gt; 相同,只是它使用std::ranges::range 概念(即您所说的“可迭代”)来限制类型。

  • requires printable&lt;std::ranges::range_value_t&lt;T&gt;&gt;T 添加了一个额外的约束:它要求std::ranges::range_value_t&lt;T&gt; 类型(T 的元素类型)满足我们的概念printable

【讨论】:

  • 这正是我想要的。我无权访问 C++20,但是当我这样做时,这正是我要做的。我还不太熟悉stackoverflow的礼仪,我应该将此答案标记为解决方案还是bolov的答案?这更接近我想要的,但 bolov 的答案更直接适用于我的需求。
  • @Peter 接受标记是你的,可以给任何你认为解决了你的问题的人。不要出汗。只要您接受答案,一切都很好。
  • @Peter 一个被接受的答案对未来的读者来说是一个很好的提示,但被接受的不是最好的并不罕见,所以无论如何人们必须阅读更多内容。有多个补充答案也很常见
  • 在 C++20 中,概念是否允许更好的错误消息?
  • @Eljay 更好的错误消息(但并非总是如此),更简单的语法(尝试编写此 C++20 之前的版本),以及这些概念相对于彼此部分排序的事实,这开启了新的函数重载的可能性。
【解决方案3】:

如果您不想使用 C++20 概念(或 Rust 特征),并且仍然希望您的函数仅适用于可迭代类型(因为没有人喜欢 SFINAE 错误),那么一种技术是要求它具有一个 const_iterator:

template <typename T>
void print_range(const T& iterable, typename T::const_iterator = T{}.cbegin()) {
    for (const auto& elem : iterable) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
}

在这种情况下, const_iterator 参数必须是默认的哑元。 我之前建议使用两个迭代器,但这需要调用者明确说明模板类型,这不是很酷。

对于选择 const_iterator/cbegin 作为可迭代对象的定义特征(通常不是这种技术)的一个公平批评是,并非所有可迭代对象都具有此接口。但这并不是真正的障碍,因为调用者可以将其包装在具有此接口的类型中。例如 C 字符串中的 string_view。

【讨论】:

  • T 永远无法推断
  • 您可能应该使用T begin, T end 来推导T
  • 让它template &lt;typename Iterator&gt; 你的例子很好
  • 不清楚为什么要保留T 作为容器类型,但无论如何都无法推断出它。 T 在你的代码中有什么用? (您实际上并没有使用它,而是可以对迭代器类型进行参数化)
  • 此外,并非所有迭代器都是某个容器的成员。示例是指针和诸如std::istream_iterator 之类的东西。
猜你喜欢
  • 1970-01-01
  • 2022-09-30
  • 2023-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-04
  • 1970-01-01
相关资源
最近更新 更多