为什么以下代码无法编译?
template <typename T>
T sum(T t){
return t;
}
template <typename T, typename ...U>
auto sum(T t, U... u) -> decltype(t + sum(u...)) {
return t + sum(u...);
}
int main() {
sum(1, 1.5, 2);
}
编译错误:
error: no matching function for call to ‘sum(int, double, int)’
sum(1, 1.5, 2);
实现此功能的好方法是什么?
答案 0 :(得分:4)
这里我们将工作转发给助手类型:
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
子句中计算自己的返回类型时无法看到自己。
有一些解决方法。上面应该可以工作,因为模板类可以在自己的主体中看到其部分特化的其他特化。另一种方法是使用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)...);
}
};
}
这使得我希望能够写出&#34;做这个总和,但在继续&#34;之前将每个子总和投射到R
。或某些。
在C ++ 1z中,您将要使用fold-expression。能够设置R
仍然有用,就像您要添加表达式模板一样,它可能仅在当前范围结束之前作为表达式模板有效。
要在C ++ 14中修复此问题,您可能必须使用R
返回值的延续传递样式。
然后我们可以将返回类型扣除折叠到游戏中以允许
Matrix m = sum( many_matrices... );
在Eigen工作(例如)。
当你第一次开始编写通用代码时,你必须问自己&#34;兔子洞的深度我们想要去哪里?&#34;
答案 1 :(得分:3)
为了完整性,因为在a similar question的这个版本上,Yakk发布了我在另一个版本中使用的模板专业化解决方案,我将提供他在那里使用的ADL解决方案:< / p>
namespace N {
struct adl {};
template <typename A, typename T>
T sum(A, T t){
return t;
}
template <typename A, typename T, typename ...U>
auto sum(A a, T t, U... u) -> decltype(t + sum(a, u...)) {
return t + sum(a, u...);
}
}
template <typename... Args>
auto sum(Args... args) -> decltype(sum(N::adl{}, args...))
{
return sum(N::adl{}, args...);
}
这样做的原因是sum
的尾随返回类型中使用的N::sum
是一个从属名称,并且[temp.dep.res]具有以下查找规则:< / p>
在解析依赖名称时,会考虑以下来源的名称:
(1.1) - 在模板定义时可见的声明 (1.2) - 来自名称空间的声明与函数参数的类型相关联 实例化上下文(14.6.4.1)和定义上下文。
由于lookup包含在定义点和定义上下文中可见的声明,N::sum
可以递归地找到它自己。
但是,我同意Yakk的说法,这种做法更令人困惑。
答案 2 :(得分:2)
引自[basic.scope.pdecl]:
名称的声明点在完成后立即生效 声明者(第8条)和初始化者(如果有的话)
第二个函数模板的声明在尾部返回类型decltype(t + sum(u...))
之后完成。因此,在解析decltype(t + sum(u...))
时,第二个模板尚未在范围内,并且编译器只能看到与调用不匹配的第一个模板。
一种可能的解决办法:
template <typename... T>
struct ReturnType;
template <typename T>
struct ReturnType<T> {
typedef T Type;
};
template <typename T, typename... U>
struct ReturnType<T, U...> {
typedef typename ReturnType<U...>::Type Type_;
typedef decltype(std::declval<T>() + std::declval<Type_>()) Type;
};
template <typename T>
T sum(T t){
return t;
}
template <typename T, typename ...U>
typename ReturnType<T, U...>::Type sum(T t, U... u) {
return t + sum(u...);
}