【问题标题】:Is there a way to define a function for different std::array without defining a template function?有没有办法在不定义模板函数的情况下为不同的 std::array 定义函数?
【发布时间】:2013-12-08 20:27:09
【问题描述】:

我正在玩 std::array 并想了解用例以及可以做什么和不可以做什么。

所以,我为自己定义了一堆类似的array-types,现在想定义一个适用于所有这些类型的函数。

#include <array>
#include <algorithm>            // accumulate, iota
#include <iostream>

using January  = std::array<int,31>;
using February = std::array<int,28>;
using March    = std::array<int,31>;
using April    = std::array<int,30>;  // ...more Months..

int main() {
    /* init */
    Januar   jan; std::iota(begin(jan), end(jan), 1); // 1..31
    February feb; std::iota(begin(feb), end(feb), 1);
    March    mar; std::iota(begin(mar), end(mar), 1);
    April    apr; std::iota(begin(apr), end(apr), 1);

    /* sum them up */
    int all = sum(jan) + sum(feb) + sum(mar) + sum(apr); // sum()-calls
}

因此,任务是定义一个函数sum,它可以在所有array 类型上工作。但由于所有这些类型都不同,我只能想到一个函数模板。

template<typename ARRAY>
int sum(const ARRAY& arr) {
    return std::accumulate(begin(arr), end(arr), 0);
}

对于那些害怕编写模板的 C++ 程序员,有没有无模板的解决方案?如何编写适用于不同array-types单个函数

【问题讨论】:

  • 咳咳。是的,我想知道但是:"...并且想了解用例以及可以做什么和不能做什么"
  • “适用于不同的array-types” 我将其解释为不同的长度,而不是不同的value_types。好吧,您基本上拥有与原始数组相同的选项,因为不同的 std::array-specializations 是不相关的。例如,您可以编写一个accumulate 函数,它接受两个int* 和一个int 作为参数(实际上是std::accumulate&lt;int*, int&gt;)。
  • @towi 你必须使用sum(array) 语法,还是sum(array.data(), array.data()+array.size()) 可以接受?
  • @yakk:可以接受。但我明显的尝试也是一个模板,带有两个迭代器参数,即template&lt;typename IT&gt; int sum(IT begin, IT end);。替代方案?

标签: c++ arrays function templates c++11


【解决方案1】:

我有一个实用程序类,它接受一个将数据存储在连续内存中的范围,并返回一个范围视图。

template<typename T>
struct contig_range_view {
  T* m_begin;
  T* m_end;
  contig_range_view(T* b, T* e):m_begin(b), m_end(e) {}
  contig_range_view():m_begin(nullptr), n_end(nullptr) {}
  contig_range_view(contig_range_view&& o):m_begin(o.m_begin), n_end(o.m_end) {}
  contig_range_view(contig_range_view& o):m_begin(o.m_begin), n_end(o.m_end) {}
  template<typename T, typename A>
  contig_range_view( std::vector<T,A>& v ):m_begin(v.data()), m_end(v.data()+v.size()) {}
  template<typename T, std::size_t N>
  contig_range_view( std::array<T,N>& a ):m_begin(a.data()), m_end(a.data()+a.size()) {}
  template<typename T, std::size_t N>
  contig_range_view( T(&a)[N] ):m_begin(&a[0]), m_end(&a[0]+N) {}
  T* begin() { return m_begin; }
  T const* begin() const { return m_begin; }
  T* end() { return m_end; }
  T const* end() const { return m_end; }
  std::size_t size() const { return m_end - m_begin; }
};

具有T const 的特化,它接受std::vector&lt;T,A&gt; const&amp;v 但阻止std::vector&lt;T,A&gt;&amp;&amp;(等)。

然后,您将函数编写为:

int sum( contig_range_view<int> values ) {
  int retval = 0;
  for( int v : values )
    retval += v;
  return retval;
}

而您的 sum 函数不是模板函数。

这里使用的技术是对容器的类型擦除视图。它的效率也高得离谱。

templates 仍然在 contig_range_view 的构造函数中使用,但它们对最终程序员用户是隐藏的。

另一个好处是,您可以编写真正通用的数组处理代码,而不必存在于头文件中。它可以毫不费力地处理C 数组、std::vectors 和std::arrays。理论上,在编译时知道要处理的内容的长度可以让函数体稍微高效一些,但这是唯一的损失。

rvalue 绑定的阻塞是因为对容器的视图立即消失是有问题的,尤其是对所述容器的非常量视图。在contig_range_view&lt;const T&gt; 案例中阻止它是有问题的:我可以选择任何一种方式。

【讨论】:

  • 为此类实现类型擦除的要点,但 OP 表示存在一些担心模板是解决方案的任何部分。 (不知道为什么)。顺便说一句,你知道你的移动构造函数做了一个复制,而不是涉及任何资源或任何东西。
  • @woolstar 目标是使编写sum 函数不需要编写任何templates,只需使用它们即可。显然使用std::array&lt;int, 27&gt; 涉及使用some templates(即std::array),但它不需要编写它们。右值构造函数存在是因为复制自const 是非法的,但复制自右值完全没问题。没有它,返回contig_range_view 的函数将无法分配给另一个contig_range_view。它不是move,而是来自右值的copy
  • @woolstar 和@Yakk:是和不是。我想了解数组实际上可以用于什么。虽然我不会自己写一个像contig_range_view 这样的好的通用包装器,但我想把它归档在“有可能使用数组的情况”下。到目前为止,我发现让所有刚开始 C++ 的程序员第二天就必须学习模板是很尴尬的。 C++11 应该更容易学习 ;-) 因此我随身携带 array 可能还不是“它”,但如果有正确的框架,它是实用的。
  • @towi array 是一种获得稍微不那么古怪的 C 风格 int x[N] 语法版本的方法。结束所有arrays 不是array,只是在一些小方面(比如从函数返回的能力)比我们之前的要好。我怀疑,远程助手会在 14 或 17 岁时出现。
  • @yakk:究竟是哪些远程助手?哪些在排队?当前的 C++1y 草案(又名 C++14)中有没有?
【解决方案2】:

有许多不同的方法可以在没有模板的情况下实现sum,但我不认为它们中的任何一种都“更好”。我能想到的一些更糟糕的方法:

1) 宏。

int sumarray(int arr[], int asz) ;

#define sum(XXX) sumarray(& XXX[0], XXX.size)

2) 继承。

除了std::array之外,定义一个所有月份都继承自的抽象基类,并定义sum()的虚拟接口。

3) 特化/多态

int sum(const std::array<int,28>& arr) { ... }
int sum(const std::array<int,29>& arr) { ... }
int sum(const std::array<int,30>& arr) { ... }
int sum(const std::array<int,31>& arr) { ... }

4) 类型擦除/pimpl

留给读者作为练习(但在内部使用模板)。

【讨论】:

  • 非常好的点! 1)宏解决方案也可以无宏完成,我喜欢。我没有想到它(回到C,耶!)。 2)矫枉过正,是我“我想学习......数组可以做什么”的一个很好的例子。 3)我的意思是一个单一的功能。 4)我猜与继承相同。好吧,谢谢,您的意见确实让我了解了哪些数组可以/应该用于什么,什么不可以。
【解决方案3】:

虽然它不是完全没有模板,但这在可怕的类型不安全指针滥用和您提供的模板之间取得了一些中间立场:

template< size_t size >
int sum( const std::array< int, size > &a ) {
    return std::accumulate( begin(a), end(a), 0 );
}

【讨论】:

  • 你当然是对的,我实现了这两个版本——类型不安全的版本只是我的第一个。你会因为指出错误的事情而获得 +1。
【解决方案4】:

作为一种完全不同的方法,我们可以利用数据存储在连续内存中这一事实。并使用原始指针作为迭代器。

我们定义sum如下:

int sum( int const* begin, int const* end );

并像这样实现它:

int sum( int const* begin, int const* end ) {
  int result = 0;
  for (int const* it = begin; it != end; ++it ) {
    result += *it;
  }
  return result;
}

现在,我们对sum 的使用看起来像:

int some = sum(jan.data(), jan.data()+jan.size());

等等。这具有看起来和编码像 C++ &lt;algorithms&gt; 的优点,但它是基于指针的,它不是写成 template

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多