如何在C++中使用通用函数?

8

我写了这段代码,如果我取消注释倒数第二行,就会出现错误:“模板参数推断/替换失败:”。这是因为C++中的通用函数有一些限制吗?此外,我的程序不会打印数组b的浮点答案。我能做些什么呢?(抱歉在单个帖子中提出两个问题。)

#include <iostream>
 using namespace std;

 template <class T>
 T sumArray(  T arr[], int size, T s =0)
 {
     int i;
     for(i=0;i<size;i++)
     {  s += arr[i];
     }
     return s;
 }

 int main()
 {
     int a[] = {1,2,3};
     double b[] = {1.0,2.0,3.0};
     cout << sumArray(a,3) << endl;
     cout << sumArray(b,3) << endl;
     cout << sumArray(a,3,10) << endl;
     //cout << sumArray(b,3,40) << endl; //uncommenting this line gives error

     return 0;
 }


编辑1:将40更改为40.0后,代码可以正常工作。这是我得到的输出:
6
6
16
46
我仍然没有在第二个案例中得到浮点答案。有什么建议吗?


1
编译器已经表明了这一点,但它从b中推断出double类型,从40中推断出int类型。它不会通过转换来猜测类型,并可能编译错误。可以轻松地让它在推断类型时忽略参数。 - chris
我注意到了你的编辑,但我强烈建议你不要在已经收到答案(或者实际上是很多答案)之后向问题主体添加新问题,因为这会使得答案有些不相关。你可能想问一个新问题,但对于这种情况,你认为答案不是浮点数吗?答案确切地说是46,无论是整数还是浮点数。尝试将一些非整数数字放入“b”中并再次检查。 - Petr
@Petr 谢谢。我会注意不在一个帖子中问两个问题。 - shane
为了避免误解,只要这两个问题(当然是相关的)从一开始就在这里提出来,那么问两个问题是可以的。 - Petr
7个回答

19

原因是编译器无法推断T的类型。

对于您的最后一个示例,它应该如何理解T呢?第一个参数(b)的类型为double[],而在函数定义中它是T[]。因此看起来T应该是double。然而,第三个参数(40)的类型是int,所以看起来T应该是int。因此出现了错误。

40更改为40.0可以使其正常工作。另一种方法是在模板声明中使用两种不同的类型:

#include <iostream>
 using namespace std;

 template <class T, class S = T>
 T sumArray(  T arr[], int size, S s =0)
 {
     int i;
     T res = s;
     for(i=0;i<size;i++)
     {  res += arr[i];
     }
     return res;
 }

 int main()
 {
     int a[] = {1,2,3};
     double b[] = {1.0,2.0,3.1};
     cout << sumArray(a,3) << endl;
     cout << sumArray(b,3) << endl;
     cout << sumArray(a,3,10) << endl;
     cout << sumArray(b,3,40) << endl; //uncommenting this line gives error

     return 0;
 }

请注意,我必须明确将s强制转换为T,否则最后一个示例将失去小数部分。
然而,这个解决方案仍然不能解决sumArray(a,3,10.1)的问题,因为它会将10.1转换为int,所以如果这也是一个可能的用例,则需要更精确的处理。一个完全工作的例子使用c++11功能可能是像这样的:
 template <class T, class S = T>
 auto sumArray(T arr[], int size, S s=0) -> decltype(s+arr[0])
 {
    int i;
    decltype(s+arr[0]) res = s;
    ...

这个模板函数的另一个可能改进是自动推导数组大小,可以参考TartanLlama的回答。

1
在代码片段 " class S = T> " 和 "T res = s" 中,我们需要使用 static_cast 吗?显式类型转换是安全的吗? - shane
@shane,对于class S=T,没有强制转换,这只是默认的模板参数。对于T res = s,我找不到好的参考资料,但一般来说,隐式类型转换比显式的static_cast更安全。后者会让编译器认为你知道自己在做什么,而前者会在出现可疑情况时发出编译错误或警告。但是,您可能需要就此提出单独的问题。 - Petr

4
sumArray(b,3,40)
< p> 40 的类型是 int ,但 b 的类型是 double [3] 。当您将这些作为参数传递时,编译器会得到 T 的冲突类型。

简单的解决方法是只传入 double :

<code>sumArray(b,3,40.0)
</code>

然而,为了更好地在调用站点进行转换,您最好添加另一个模板参数。您还可以添加一个参数来推断数组的大小,以便您不需要显式传递它:

template <class T, class U=T, std::size_t size>
U sumArray(T (&arr) [size], U s = 0)

为支持s的默认值,默认情况下将U参数设置为T。请注意,为了推导数组的大小,我们需要传递对它的引用而不是按值传递,否则它会被降解为指针。

现在的调用看起来像这样:

sumArray(b,40)

Live Demo


请注意,仅仅引入U可能是不够的,因为如果U是浮点型但T是整数型,它将会产生浮点转整型的问题。 - Petr
1
@Petr 你说得对,你的解决方案更好。我会保留这个答案来自动推断数组大小,除非你想在你的答案中加入它。 - TartanLlama
你可以通过 template <class C, class T = std::decay_t<decltype(*std::begin(std::declval<const C&>()))>> T sumArray(const C&, T = T{}) 进一步实现。 - Jarod42

3

In

template <class T>
T sumArray(  T arr[], int size, T s =0)
             ^                  ^

需要匹配的(可以推导的)T应该相同。

sumArray(b, 3, 40) 中,第一个应该是 double,第二个应该是 int

有几种可能的方法来解决这个问题。

  • at the call site, call sumArray(b, 3, 40.0) or sumArray<double>(b, 3, 40);

  • Use extra parameter:

    template <typename T, typename S>
    auto sumArray(T arr[], int size, S s = 0)
    

    Return type may be T, S, or decltype(arr[0] + s) depending of your needs.

  • make a parameter non deducible:

    template <typename T> struct identity { using type = T;};
    // or template<typename T> using identity = std::enable_if<true, T>;
    
    template <typename T>
    T sumArray(T arr[], int size, typename identity<T>::type s = 0)
    

1
我在寻找一个提到非推导参数的答案,你给了它 :) - Quentin
@Deduplicator:我认为std::enable_if这个名称对于这种情况来说有些误导性。但是template<typename T> using identity_t = std::enable_if_t<true, T>;似乎是一个不错的折中方案。 - Jarod42

2

应该是

sumArray(b,3,40.0)

所以,T 将被推断为 double。在你的代码中,它是 int

1
当推断失败时,另一个选项是明确告诉编译器你的意思:
cout << sumArray<double>(b,3,40) << endl;

1
请注意,STL在可能的情况下倡导不要这样做 - chris
@chris 偶尔这样做可能更清晰,但是通常我会使用其他选项之一。 - Alan Stokes

0
编译器不知道 T 应该是 int 还是 double
为了保留传递类型的最高精度,您可能希望执行以下操作:
template <class T, class S>
std::common_type_t <T, S> sumArray (T arr [], std::size_t size, S s = 0)
{
   std::common_type_t <T, S> sum = s;
   for (std::size_t i = 0; i != size; ++i)
   {
      sum += arr[i];
   }
   return sum;
}

你正在编写的函数已经存在了。它就是 std::accumulate
std::cout << std::accumulate (std::begin (b), std::end (b), 0.0) << std::endl;

请注意,如果初始值不是“正确”的类型(使用0而不是0.0会导致返回类型为int而不是double),则可能会误用accumulate函数。 - Jarod42

-1

模板只接受一种数据类型,例如如果你发送一个双精度数组,那么运行时将推导:

模板 = double[]

因此每次它看到它时,它都期望是一个双精度数组。

sumArray(b,3,40) 传递了 "b"(它是一个双精度数组),但是然后你传递了 "40" ,这个运行时无法隐式转换为双精度。

所以这个代码

sumArray(b,3,40.0)

会起作用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接