[英]Variadic template recursive return type deduction compilation error
为什么以下代码无法编译?
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);
实现此功能的好方法是什么?
在这里,我们将工作转发给帮助器类型:
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
,我可以选择采用我期望它返回的类型,如果是,它会接受零长度总和(创建默认实例),但不要求类型如果我传递一个或多个参数,则为默认构造。 但我喜欢过度设计的通用代码:
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中,您将需要使用fold-expression。 能够设置R
仍然有用,就像您要添加表达式模板一样,它可能仅在当前范围结束之前作为表达式模板有效。
要在C ++ 14中解决此问题,您可能必须使用具有R
返回值的continuation传递样式。
然后我们可以将返回类型扣除折叠到游戏中以允许
Matrix m = sum( many_matrices... );
在Eigen工作(例如)。
当你第一次开始编写通用代码时,你必须问自己“兔子洞到底有多深?”
为了完整性,因为在这个版本的类似问题上 , Yakk发布了我在另一个中使用的模板专业化解决方案,我将提供他在那里使用的ADL解决方案:
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]以下查找规则:
在解析依赖名称时,会考虑以下来源的名称:
(1.1) - 在模板定义时可见的声明。
(1.2) - 来自实例化上下文(14.6.4.1)和定义上下文中与函数参数类型相关联的名称空间的声明。
由于查找包括在定义点和定义上下文中可见的声明,因此N::sum
可以递归地找到它自己。
但是,我同意Yakk的说法,这种做法更令人困惑。
引自[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...);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.