【问题标题】:how do I write a function that can iterate over a generic type in a typesafe way [closed]如何编写一个可以以类型安全的方式迭代泛型类型的函数[关闭]
【发布时间】:2015-12-15 23:44:57
【问题描述】:

我想编写一个函数,它可以以类型安全的方式在给定的泛型类型上输入迭代器。一个可能的用例是编写一个函数,如累积/映射/折叠:

#include <iterator>
#include <vector>
#include <functional>

template <typename V, typename K>
K accumulate(
      std::function<K(K, V)> accumulator,
      /* WHAT TYPE DO I PUT HERE */ it,
      /* WHAT TYPE DO I PUT HERE */ end,
      K initial) {
  K sum = initial;
  for (; it != end; ++it) {
    V item = *it;
    sum = accumulator(sum, item);
  }
  return sum;
}

我怎样才能以编译器检查类型和所有好东西的方式做到这一点?

以前问过here

【问题讨论】:

  • 不清楚你在问什么。编译器无论如何都会检查类型。请澄清一下,“所有这些好东西”。
  • 同时,您可以从HERE 的实践中获得一些类似 STL 的实现的灵感。例如,您的accumulate。当然,您可以随时打开您喜欢的标准库的头文件并阅读它们。附言让我们为 C++17 和concepts祈祷。
  • Sam 希望避免像bool a = false, b = true; bool *ap = &amp;a, *bp = &amp;b; print_all(ap, bp); 这样会通过基本类型检查的废话。
  • @user4581301 如果您想检测到,请运行消毒剂。
  • 也许我遗漏了一些明显的东西,但在我看来,模板只需要另一个参数,“typename I”,“I”变成“我应该放什么”。

标签: c++ c++11 generics types


【解决方案1】:

我想我理解了你的问题,我可以想到两种不同的方法,各有利弊:

[1] 为迭代器使用第三种模板类型,比如 ITER。优点是代码更简单,适用于更多集合类型,限制更少。缺点是,没有捕捉到 ITER 和 V 之间的依赖约束,即 ITER 必须是一个迭代类型 V 的迭代器,即 V = ITER::value_type。

[2] 显式调用依赖类型而不是创建新的模板参数。它更准确地捕获依赖类型,更少的模板参数。缺点是,它依赖于可能不是标准的集合的类型声明(如果 ITER 没有子类型 ITER::value_type 怎么办,或者它的名称不同?)。您将在此处为依赖类型使用关键字类型名。 在这里,编译器可以更好地处理编译错误,但是请注意,除非您实际实例化它,否则您几乎不会得到任何关于类型错误的反馈。所以你需要用 2/3 具体类型来测试代码。

C++ 代码说明了这两种方法。顺便说一句,你为什么使用 typename,我认为它应该只是“模板

template <class V, class K, class ITER>
K accumulate1(
      std::function<K(K, V)> accumulator,
      ITER it,
      ITER end,
      K initial) {
  K sum = initial;
  for (; it != end; ++it) {
    V item = *it;
    sum = accumulator(sum, item);
  }
  return sum;
}

template <class K, class ITER>
K accumulate2(
      std::function<K(K, typename ITER::value_type)> accumulator,
      ITER it,
      ITER end,
      K initial) {
  K sum = initial;
  for (; it != end; ++it) {
    typename ITER::value_type item = *it;
    sum = accumulator(sum, item);
  }
  return sum;
}

string AppendInt(const string& s, int n) {
  char buffer [65];
  sprintf(buffer, "%s/%d", s.c_str(), n);
  return string(buffer);
}

int main(int argc, char* argv[]) {
  std::function<string(string,int)> fun =
      [](string s, int n) -> string { return AppendInt(s,n); };
  string initial = "x";
  vector<int> array = {13, 24, 50, 64, 32};
  string sum1 = accumulate1(fun, array.begin(), array.end(), initial);
  string sum2 = accumulate2(fun, array.begin(), array.end(), initial);
  printf("accumulate1 : %s\n", sum1.c_str()); // o/p: x/13/24/50/64/32
  printf("accumulate2 : %s\n", sum2.c_str()); // o/p: x/13/24/50/64/32
}

【讨论】:

  • 补充几点,这是一般准则,尽可能少使用独立类型,使用更多依赖类型/推导类型。在 C++ STL 编译器不会产生太多类型错误(与 Java 不同),只有在实际调用/实例化编译器才会进行类型检查。例如您可以混合使用两种类型 K 和 V,并将一种类型分配给另一种类型,当您使用相同类型实例化时不会被捕获,例如
【解决方案2】:

研究标准库容器中使用的模式。问题是你试图明确容器应该定义的一些类型。此外,谨慎使用auto 意味着您根本不需要显式声明某些类型。

template <typename C, typename R>
R accumulate(
      std::function<R(R, C::value_type)> accumulator,
      R initial
) {
  auto sum = initial;
  for (auto it = C.begin(); it != C.end(); ++it) {
    sum = accumulator(sum, *it);
  }
  return sum;
}

通过使用容器类型作为模板参数,您可以依赖其value_type 定义来确保累加器函数的类型安全。通过将auto 用于迭代器并使用标准的begin()end() 方法,您甚至不必明确提及它们的类型。 (可以肯定的是,如果您需要,他们是 C::iterator。)

【讨论】:

    猜你喜欢
    • 2016-03-21
    • 2023-03-20
    • 1970-01-01
    • 2020-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-22
    • 1970-01-01
    相关资源
    最近更新 更多