【问题标题】:How to pass an array size as a template with template type?如何将数组大小作为模板类型的模板传递?
【发布时间】:2010-10-03 02:51:51
【问题描述】:

当我尝试将固定大小的数组传递给模板函数时,我的编译器行为异常。代码如下:

#include <algorithm>
#include <iostream>
#include <iterator>

template <typename TSize, TSize N>
void f(TSize (& array)[N]) {
    std::copy(array, array + N, std::ostream_iterator<TSize>(std::cout, " "));
    std::cout << std::endl;
}

int main() {
    int x[] = { 1, 2, 3, 4, 5 };
    unsigned int y[] = { 1, 2, 3, 4, 5 };
    f(x);
    f(y); //line 15 (see the error message)
}

它在 GCC 4.1.2 中产生以下编译错误:

test.cpp|15| error: size of array has non-integral type ‘TSize’
test.cpp|15| error: invalid initialization of reference of type
  ‘unsigned int (&)[1]’ from expression of type ‘unsigned int [5]’
test.cpp|6| error: in passing argument 1 of ‘void f(TSize (&)[N])
  [with TSize = unsigned int, TSize N = ((TSize)5)]’

请注意,第一次调用编译并成功。这似乎暗示虽然int 是不可或缺的,但unsigned int 不是。

但是,如果我将上述函数模板的声明更改为

template <typename TSize, unsigned int N>
void f(TSize (& array)[N])

问题就解决了!请注意,这里唯一的变化是从 TSize Nunsigned int N

最终草案 ISO/IEC FDIS 14882:1998 中的 [dcl.type.simple] 部分似乎暗示“整数类型”是有符号或无符号的:

signed 说明符强制 char 对象和位域被签名;它与其他整数类型是多余的。

关于固定大小的数组声明,草案说 [dcl.array]:

如果constant-expression (expr.const) 存在,它应该是一个整数常量表达式,并且它的值应该大于零。

那么,为什么我的代码可以使用显式的 unsigned 尺寸类型、推断的 signed 尺寸类型但不能使用推断的 unsigned 尺寸类型?

编辑 Serge 想知道我在哪里需要第一个版本。首先,这个代码示例显然被简化了。我的真实代码要复杂一些。该数组实际上是另一个数组中的索引/偏移量数组。 因此,从逻辑上讲,数组的类型应该与其大小类型相同,以获得最大的正确性。 否则,我可能会得到类型不匹配(例如,unsigned intstd::size_t 之间)。诚然,这在实践中应该不是问题,因为编译器会隐式转换为两种类型中较大的一种。

编辑 2 我已得到纠正(谢谢,litb):大小和偏移量当然在逻辑上是不同的类型,尤其是 C 数组的偏移量是 std::ptrdiff_t 类型。

【问题讨论】:

  • 第一个示例使用 Microsoft 编译器 (2005) 编译得很好。 Digital Mars 在“void f(TSize (& array)[N]) {”行中抱怨“错误:预期的积分表达式”
  • 你能举个例子,你绝对必须使用第一个版本吗?
  • 在关于基本类型的析构函数的线程中被确认不能直接调用~int,而是在模板中执行 typedef int MyType;调用 ~MyType 是合法的。但我认为正确的做法是使用 size_t 作为第二个参数。
  • 这绝对是有趣的康拉德。至少,comeau 接受该代码。
  • Serge,litb,请将您的 cmets 变成答案,以便其他人可以更快地找到它们(因为我认为它们是相关的)。 @xhantt:我不确定析构函数与此有什么关系。在我的函数中,任何地方都不应调用 TSize 的构造函数(甚至是隐式调用)。

标签: c++ arrays templates type-inference


【解决方案1】:

嗯,标准在14.8.2.4 / 15 中说:

如果在声明具有非类型模板参数的函数模板中,在函数参数列表中的表达式中使用了非类型模板参数,并且如果推导出相应的模板参数,模板参数类型应与模板参数的类型完全匹配,除了从数组绑定推导出的模板参数可以是任何整数类型。

提供这个例子:

template<int i> class A { /* ... */ };
template<short s> void f(A<s>);
void k1() {
    A<1> a;
    f(a);    // error: deduction fails for conversion from int to short
    f<1>(a); // OK
}

这表明无法编译您的代码的编译器(显然是 GCC 和 Digital Mars)做错了。我用 Comeau 测试了代码,它可以很好地编译你的代码。我认为非类型模板参数的类型是否取决于类型参数的类型没有什么不同。 14.8.2.4/2 表示模板参数应该相互独立推导,然后组合成函数参数的类型。结合 /15,它允许维度的类型是不同的整数类型,我认为你的代码都很好。与往常一样,我使用 c++-is-complicated-so-i-may-be-wrong 卡:)

更新:我查看了 GCC 中的段落,它吐出了错误消息:

  ...
  type = TREE_TYPE (size);
  /* The array bound must be an integer type.  */
  if (!dependent_type_p (type) && !INTEGRAL_TYPE_P (type))
    {
      if (name)
    error ("size of array %qD has non-integral type %qT", name, type);
      else
    error ("size of array has non-integral type %qT", type);
      size = integer_one_node;
      type = TREE_TYPE (size);
    }
  ...

似乎错过了在较早的代码块中将大小的类型标记为依赖。由于该类型是模板参数,因此它是依赖类型(请参阅14.6.2.1)。

更新: GCC 开发者修复了它:Bug #38950

【讨论】:

  • 我认为您可能是对的。感谢您查阅相关文章。
  • litb,您提交了错误报告或提交了补丁吗?这可能应该完成。我坦率地承认,我被 kraken GCC 项目吓到了,也不知道该怎么做。
  • 康拉德·鲁道夫,我以为我会为你离职,这样你就可以获得名声,但如果你没有时间或不想做,我会做.在这种情况下,请留下另一条评论。
  • 非常感谢。我很感激。
  • 重新修正错误:这太酷了!有多少 StackOverflow 讨论导致错误修复的统计数据?我至少认识一个人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-24
  • 2011-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多