我试图了解如何正确实现一个函数来计算数学示例均值,具有两个初始必需的特征:
1(使用variadic参数。
2(不使用两个函数来完成这项工作,也就是说,不使用呼叫者函数,然后是实际完成计算的第二个函数。
3(函数应尽可能通用
我非常清楚一个非常相似的问题已经问:但是,使用variadic-template功能计算几个值的平均值他不知道,它提出了一个实际上是错误且没有编译的代码。
所以,我自己的第一次尝试是沿着这些行:
template <class... Args>
double mean(const Args & ... args)
{
auto total = 0;
for (auto value : { ...args })
{
total += value;
}
return (double)total / sizeof...(args);
}
这里的问题是,在auto total = 0;
行中,编译自然无法自动识别对象total
应具有的类型。
然后,我的第二次尝试:
template <class T, class... Args>
T mean(const T &t, const Args & ... args)
{
T total = 0;
for (auto value : { args... })
{
total += value;
}
return (T)(total / sizeof...(args));
}
该版本以下问题。如果呼叫者用混合类型的参数调用函数,例如在mean(1, 2.5)
中,它不起作用,其中第一个参数自动检测为int
,第二个参数被检测为double
。
我能够通过执行以下操作来解决这一点:
template <class T, class... Args>
T mean(const T &t, const Args & ... args)
{
size_t argsSize = sizeof...(args);
T total = t;
T arg_array[] = { args... };
for (size_t i = 0; i< argsSize; i++)
{
total += (T)arg_array[i];
}
return (T)(total / argsSize) ;
}
即使传递的参数为不同的类型(当然,只要可以将这些类型转换为T
(即使是不同的类型(。但是,现在的问题是该函数仅适用于至少两个参数。如果像mean(3.14)
中的那样称呼它,虽然应该返回3.14,但实际上会引起错误,因为无法编译T arg_array[] = { args... }
,因为不可能使用size 0
创建静态数组。当然,我可以将其替换为一个动态数组,但这会使我每次称为函数时都必须进行一个内存分配和一个内存DEADLOCATION-这是一个不可接受的浪费。
那么,实现避免上述问题并遵循我的两个初始条件的函数的正确方法是什么?
使用std::common_type_t
:
template<class... Args> constexpr auto mean(Args... args) {
std::common_type_t<Args...> total(0);
for(auto value : {std::common_type_t<Args...>(args)...}) total += value;
return total/sizeof...(args);
}
使用C 17
中的折叠表达式template<typename... Args>
constexpr decltype(auto) mean(Args&&... args)
{
return (... + std::forward<Args>(args)) / sizeof...(Args);
}
std::forward
有一些支持移动的bignum类型。
在C 17之前,需要手动折叠,但是对于基本情况来说,这将需要过载mean
,这似乎不是您想要的。
解决"该函数仅适用于两个参数"的问题您可以创建一个简单的类型特征来提取第一个类型
template <typename T0, typename ...>
struct firstType
{ using type = T0; };
或,如果您喜欢(并且,恕我直言,更好(,请遵循diego91b( 1(的std::common_type_t
建议。
您可以在C 11中编写mean()
,如下所示
template <typename ... Args>
typename firstType<Args...>::type mean (Args const & ... args)
// or typename std::common_type<Args...>::type mean (Args const & ... args)
{
using unused = int[];
typename firstType<Args...>::type total { 0 };
// or typename std::common_type<Args...>::type total { 0 };
(void)unused { (total += args, 0)... };
return total / sizeof...(args);
}
在C 14中可以被半序列化(并在constexpr
中转换(,如下所示
template <typename ... Args>
constexpr auto mean (Args const & ... args)
{
using unused = int[];
//typename firstType<Args...>::type total { 0 };
std::common_type_t<Args...> total { 0 };
(void)unused { (total += args, 0)... };
return total / sizeof...(args);
}
在C 17中,您可以使用折叠表达式(如Passer所建议(,并且变得非常简单
template <typename ... Args>
constexpr typename firstType<Args...>::type mean (Args const & ... args)
// or constexpr auto mean (Args const & ... args)
{
return (... + args) / sizeof...(Args);
}
以下是完整的可编译C 11示例
#include <iostream>
#include <type_traits>
template <typename T0, typename ...>
struct firstType
{ using type = T0; };
template <typename ... Args>
typename firstType<Args...>::type mean (Args const & ... args)
// or typename std::common_type<Args...>::type mean (Args const & ... args)
{
using unused = int[];
typename firstType<Args...>::type total { 0 };
// or typename std::common_type<Args...>::type total { 0 };
(void)unused { (total += args, 0)... };
return total / sizeof...(args);
}
int main()
{
std::cout << mean(2, 5.5) << std::endl; // print 3 (3.75 with common_type)
std::cout << mean(5.5, 2) << std::endl; // print 3.75
std::cout << mean(2) << std::endl; // print 2
// std::cout << mean() << std::endl; compilation error
}
- 编辑 -
op ask
您知道为什么您的解决方案在使用common_type时确切地给出了3个时给出3.75(2,5.5(?
确定。
使用firstType
调用mean(2, 5.5)
时,第一个参数的类型是2
的类型,即int
。因此,total
被声明为int
,当将5.5
添加到Total时,将转换为int
,因此成为5
,AD添加到2
中。total
成为7
并由2
分配,因此(整数部门(成为3
。
在国家/地区,如果您致电mean(5.5, 2)
,则firstType
是5.5
的类型,即double
。因此,total
成为double
,添加2
和5.5
并获得7.5
。最后一个部门(7.5 / 2
(是double
分区,给出3.75
。
使用std::common_type_t
调用mean(2, 5.5)
(或mean(5.5, 2)
(时,int
和double
之间的公共类型为double
;因此,total
定义为double
,函数返回7.5 / 2
为3.75
。
- 编辑2 -
op ask
必须专门为
int[]
分配使用未使用的特定原因?我尝试将其更改为typename std::common_type<Args...>::type
,在我看来,这将使功能更通用,但随后没有编译。另外,它不适用于double[]
。
unused
变量是...未使用。算在内,仅在其初始化中允许添加 total
args
的值,解开它们。
诀窍是逗号:观察
(total += args, 0)...
逗号丢弃左值(已计算,因此total
已修改但已删除(并返回正确的值。那是零。整数零。因此,是int[]
。
最终结果是unused
用{ 0, 0, 0, ... }
初始化。
无需以不同类型的方式进行转换。这只是使用args
包的技巧。
如果愿意,可以(以身例子(float
;如下
using unused = float[];
(void)unused { (total += args, 0.0f)... };
(观察0.0f
(但是...我们为什么要这样做?