【问题标题】:In a template argument, what rules allow the compiler to infer the number of items of an array?在模板参数中,哪些规则允许编译器推断数组的项数?
【发布时间】:2021-04-13 14:42:24
【问题描述】:

鉴于下面的两个程序——除了模板函数len()的定义之外完全相同:

// ================================
//  a.cc
// ================================
#include <iostream>

template<class T, std::size_t n>
std::size_t len(T (&v)[n]) { return n; }

int main()
{
    int arr[] = { 1,2,3 };

    std::cout << len(arr) << std::endl;
}
// ================================
//  b.cc
// ================================
#include <iostream>

template<class T, std::size_t n>
std::size_t len(T v[n]) { return n; }

int main()
{
    int arr[] = { 1,2,3 };

    std::cout << len(arr) << std::endl;
}

使用 g++ 时,第一个程序按预期编译和运行。但第二个不编译。这是诊断:

b.cc: In function ‘int main()’:
b.cc:13:25: error: no matching function for call to ‘len(int [3])’
     std::cout << len(arr) << std::endl;
                         ^
b.cc:7:13: note: candidate: template<class T, long unsigned int n> std::size_t len(T*)
 std::size_t len(T v[n]) { return n; }
             ^
b.cc:7:13: note:   template argument deduction/substitution failed:
b.cc:13:25: note:   couldn't deduce template parameter ‘n’
     std::cout << len(arr) << std::endl;

为什么编译器能够在第一种情况下推断出数组中的项目数,而在第二种情况下却不能?是什么使得在 C++11 或 C++17 标准中必须编写 T (&amp;v)[n] 而不是 T v[n]

【问题讨论】:

  • 我不打算去寻找参考(因为我觉得这是一个相当众所周知的事实),但是函数参数中的T x[N]完全,完全等同于T *x
  • ... 但T(&amp;x)[N] 不等于T * const (&amp;x) ?!?
  • 不,此规则不适用于递归。它仅适用于数组。 T x[N] 是一个数组,所以它适用于它,T(&amp;x)[N] 是一个引用而不是数组,所以它不适用(对数组的引用,是的,但它的引用无关紧要)。
  • 我想我明白你的意思了。谢谢。

标签: c++ c++11 templates c++17 language-lawyer


【解决方案1】:

[temp.deduct] [emphasis mine] 涵盖了模板参数推导:

/1 当一个函数模板特化被引用时,所有的 模板参数应该有值。 值可以明确 指定或,在某些情况下,从使用中推断或获得 来自默认模板参数。

/2(...关于显式模板参数列表:此处不相关)

/3 执行完这个替换后,函数参数类型 执行 [dcl.fct] 中描述的调整。 [ 示例:A “void (const int, int[5])”的参数类型变为 “无效()(int,int)”。 —结束示例] [...]

注意/3中对[dcl.fct]和相关(非规范)示例的引用,显示将值类型数组函数参数int[N]调整为int*,表示非类型模板参数N 不能从传递给值类型数组参数的参数中推导出来(N 在这种情况下无用/被忽略)。

从函数调用中推导模板参数,特别是,[temp.deduct.call] 涵盖;区分您的两个示例的相关部分是[temp.deduct.call]/2.1,它表示如果调用的参数(表示为A)是数组类型,并且参数类型(表示为P)是not 引用类型,指针类型用于类型推导:

/2 如果P 不是引用类型:

  • (2.1) 如果A是数组类型,则使用数组到指针标准转换产生的指针类型代替A的类型 扣除;否则,[...]

而当P 引用类型时,如下例所示:

template<class T, std::size_t n>
std::size_t len(T (&v)[n]) { return n; }

[temp.deduct.call]/2.1 不适用,并且对于以下以数组为参数调用名为len 的函数

int arr[] = { 1,2,3 };
(void)len(arr);

名称查找将找到函数模板len,随后将使用P 作为T[N](根据[temp.deduct.call]/3)和A 作为单个函数参数的int[3] 应用模板参数推导的len,推导TintN3,为参数类型P的完整类型推导;根据[temp.deduct.type]/1:

模板参数可以在几种不同的上下文中推导出来,但是 在每种情况下,都是根据模板参数指定的类型 (称为P)与实际类型(称为A)进行比较,然后 尝试查找模板参数值(类型的类型 参数,非类型参数的值,或模板 模板参数),这将使P,在替换后 推导值(称为推导A),兼容A

即非类型模板参数N是单个函数参数的类型的一部分(类型模板参数T也是如此)。

【讨论】:

    【解决方案2】:

    在这个:

    template<class T, std::size_t n>
    std::size_t len(T (&v)[n]) { return n; }
    

    v 是对T[n] 数组的引用。数组的大小 (n) 是数组类型的一部分。所以n可以从传入的任何固定大小的数组中推导出来。

    在这个:

    template<class T, std::size_t n>
    std::size_t len(T v[n]) { return n; }
    

    在函数参数T v[n] 中,n 被忽略,T v[] 只是T *v 的语法糖(您可以在错误消息中看到 - std::size_t len(T*))。因此,即使传入了一个固定长度的数组,实际上也没有什么可以推断出n

    【讨论】:

      【解决方案3】:

      第一个相当于一个指针。请参阅 dcl.fct(由 temp.deduct 调用,正如@dfrib 的回答所解释的那样):

      函数的类型使用以下规则确定。 ...确定每个参数的类型后,将任何类型为“T”的参数...调整为“指向T”的指针。 ...

      这不适用于第二个,因为它是对数组的引用(没有引用数组,但对数组的引用很好)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-01-21
        • 1970-01-01
        • 2015-12-29
        • 1970-01-01
        • 2015-03-13
        • 2021-07-18
        • 2013-12-09
        相关资源
        最近更新 更多