在这里,我们将工作转发给辅助类型:
namespace details {
template<class...Ts>
struct sum_t {};
template<class T>
struct sum_t<T> {
T operator()(T t)const{ return std::forward<T>(t); }
};
template<class T, class...Ts>
struct sum_t<T,Ts...> {
auto operator()(T t, Ts...ts)const
-> decltype( std::declval<T>() + sum_t<Ts...>{}(std::declval<Ts>()...) )
{
return std::forward<T>(t) + sum_t<Ts...>{}(std::forward<Ts>(ts)...);
}
};
}
template<class...Ts>
auto sum(Ts...ts)
-> decltype( details::sum_t<Ts...>{}(std::declval<Ts>()...) )
// -> std::result_of_t<details::sum_t<Ts...>(Ts...)>
// above line is C++14 and cleaner version of previous line
{
return details::sum_t<Ts...>{}(std::forward<Ts>(ts)...);
}
基本问题是模板函数在计算自己的返回类型时无法看到自己-> decltype
clause.
有一些解决方法。上面的代码应该可以工作,因为模板类可以在其自己的主体中看到其部分特化的其他特化。另一种方法是使用 Koenig 查找 (ADL) 推迟对其递归调用的搜索,直到实例化点,在那里它可以找到自己。我发现第二种方法更令人困惑。
如果我要自己写sum
对于生产,我可以选择采用我期望它返回的类型,如果它这样做,它将接受零长度和(创建默认实例),但如果我传递 1 或,则不要求该类型是默认可构造的更多的争论。但我喜欢过度设计的通用代码:
template<class R0=void,class...Ts,class R=std::conditional_t<
!std::is_same<R0,void>{},
R0,
std::result_of_t<details::sum_t<Ts...>(Ts...)>
>>
R sum(Ts...ts)
{
return details::sum_t<R, Ts...>{}(std::forward<Ts>(ts)...);
}
我修改的地方sum_t
将返回类型作为第一个参数:
namespace details {
template<class R,class...Ts>
struct sum_t {
R operator()()const{ return {}; }
};
template<class R, class T>
struct sum_t<R, T> {
using R0 = std::conditional_t<!std::is_same<R,void>{},R,T>;
R0 operator()(T t)const{ return std::forward<T>(t); }
};
template<class R, class T, class...Ts>
struct sum_t<R, T,Ts...> {
using R0 = std::conditional_t<
!std::is_same<R,void>{},
R,
decltype( std::declval<T>() + sum_t<void,Ts...>{}(std::declval<Ts>()...) )
>;
R0 operator()(T t, Ts...ts)const
{
return std::forward<T>(t) + sum_t<void,Ts...>{}(std::forward<Ts>(ts)...);
}
};
}
这让我希望能够写“计算总和,但将每个子总和转换为R
在继续之前”或类似的内容。
在 C++1z 中,您需要使用折叠表达式。能够设定R
仍然有用,就像您正在添加表达式模板一样,它可能仅在当前作用域结束之前作为表达式模板有效。
要在 C++14 中解决此问题,您可能必须使用连续传递样式R
返回值。
然后我们可以将返回类型推导折叠到游戏中以允许
Matrix m = sum( many_matrices... );
在 Eigen 工作(例如)。
当你第一次开始编写通用代码时,你必须问自己“我们想要进入兔子洞多深?”