我试图了解如何正确实现一个函数来计算数学样本均值,具有两个初始必需特征:
1)使用可变参数。
2)不使用两个函数来完成这项工作,即不使用调用函数,而是使用实际执行计算的第二个函数。
3)功能应尽可能通用
我非常清楚已经提出了一个非常类似的问题:Calculate the average of several values using a variadic-template function然而,虽然这个问题的公认答案似乎已经教会了OP怎么做他不知道的小部分,它提供了一个实际上错误且无法编译的代码。
所以,我自己的第一次尝试就是这样:
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 {创建静态数组{1}}。当然,我可以用它替换动态数组,但是这会让我每次调用函数时都要做一次内存分配和一次内存释放 - 这是一种不可接受的浪费。
那么,实现这样一个避免上述问题并且遵循我的两个初始条件的函数的正确方法是什么?
答案 0 :(得分:4)
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);
}
答案 1 :(得分:2)
在C ++ 17中使用fold expressions
template<typename... Args>
constexpr decltype(auto) mean(Args&&... args)
{
return (... + std::forward<Args>(args)) / sizeof...(Args);
}
std::forward
适用于支持移动的一些bignum类型。
在C ++ 17之前,需要手动折叠,但这需要为基础案例重载mean
,这似乎不是你想要的。
答案 2 :(得分:0)
为了解决&#34;该功能仅适用于至少两个参数&#34;你可以创建一个简单的类型特征来提取第一个类型
template <typename T0, typename ...>
struct firstType
{ using type = T0; };
或者,如果您愿意(并且,恕我直言,更好),请遵循Diego91b的std::common_type_t
建议(+1)。
您可以在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 By所建议的那样)并且变得非常简单
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问
你知道为什么你的解决方案给出了3,而使用common_type在调用mean(2,5.5)时得到3.75?
不确定
使用mean(2, 5.5)
致电firstType
时,第一个参数的类型是2
的类型,即int
。因此,total
被声明为int
,当5.5
被添加到总计时,会转换为int
,因此变为5
,广告被添加到2
}。 total
变为7
并除以2
,因此(整数除法)变为3
。
在计票上,如果您致电mean(5.5, 2)
,则firstType
的{{1}}类型为5.5
。因此double
成为total
,添加double
和2
并获得5.5
。最后一个部门(7.5
)是7.5 / 2
部门,提供double
。
使用3.75
致电mean(2, 5.5)
(或mean(5.5, 2)
)时,std::common_type_t
和int
之间的常见类型为double
;因此double
定义为total
,函数返回double
7.5 / 2
。
- 编辑2 -
OP问
为什么使用未使用的必须专门分配
3.75
是否有特殊原因?我尝试将其更改为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
)但是......为什么我们应该这样做?