推断函子返回类型的通用方法?
Posted
技术标签:
【中文标题】推断函子返回类型的通用方法?【英文标题】:Generic way to deduce the return type of a functor? 【发布时间】:2012-08-15 13:04:30 【问题描述】:这个问题是How to deduce the type of the functor's return value?的后续问题 我正在以更抽象的方式重新制定它。
给定一个模板函数的伪代码
template <typename Arg, typename Fn>
auto ComputeSomething(Arg arg, Fn fn) -> decltype(<decl-expr>)
// do something
// ............
return fn(<ret-expr>)
其中<ret-expr>
是任意表达式,其中涉及arg
,我应该使用<decl-expr>
来设置ComputeSomething
的返回类型等于函子的返回类型。
函子可以是类、lambda 或函数指针。
目前我找到的部分解决方案。
(a) 我的链接问题的答案由 ecatmur 完成。本质上,它在<decl-expr>
中重复返回语句。问题:容易出错,如果包含局部变量则无法工作。
(b) 它只适用于函数指针
template <typename Arg, typename Ret>
Ret ComputeSomething(Arg arg, Ret(*fn)(Arg))
(c) 它假设函子的参数是Arg
类型(一般情况下可能不成立)并且要求Arg
是默认可构造的
template <typename Arg, typename Fn>
auto ComputeSomething(Arg arg, Fn fn) -> decltype(fn(Arg())
(d) 使用std::declval
应该解除默认可构造的限制,如how to deduce the return type of a function in template 中所建议的那样。谁能解释一下它是如何工作的?
template <typename Arg, typename Fn>
auto ComputeSomething(Arg arg, Fn fn) -> decltype(fn(std::declval<Arg>())
【问题讨论】:
很抱歉这么说,但 AFAIK 这是不可能的,因为尾随返回没有看到函数模板被定义,而正文却看到了。 【参考方案1】:使用result_of。它是向后兼容的,可以消除代码中所有丑陋的declval
痛苦。如果您实际上只是转发值,您仍然需要记住添加右值引用限定符 (&&
)。
我认为重要的其他事情:您的函数将参数转发给另一个函数。在这种情况下,您应该始终使用右值引用来传递参数。
如果您想要做的只是提高可维护性:在 RETURNS
宏上进行了多次尝试,以尽量减少返回类型声明和实际返回表达式之间的重复,但我没有看到任何允许函数体包含比实际返回语句更多的内容。
至于declval
的工作原理:取决于它的编译器。它不允许出现在评估的内容中,并且它的参数可以是不完整的类型。见20.2.4
【讨论】:
我建议不要使用std::result_of
。与基于decltype
的解决方案不同,这一次不能保证SFINAE。它也不反映调用的结果类型——它的作用不止于此,例如与指向成员的指针有关。
@LucDanton 我一直认为“做得更多”是一种好处。处理指向成员函数的指针的情况非常乏味,result_of
解决了这个问题。您能否详细介绍一下 SFINAE 问题?
std::result_of
计算函数模板无法处理的结果类型毫无意义 -- 当f
是指向成员的指针时,f(a, b, c)
是语法错误。至于 SFINAE,考虑一个重载的apply
,这样apply(f, args...)
要么导致f(args...)
,或者,如果它的格式不正确,则求助于f(*args...)
(为了简单起见,我们假设这两个表达式是永远不会同时形成良好)。您需要 SFINAE 来消除任一重载以使调用成功。
@LucDanton 如果您想同时处理原始成员函数指针和普通函数,或者只需要将其包装在mem_fn
中,您通常不会直接发送某种call_traits
。我可能必须完成 SFINAE 部分,因为我不明白。
这与函数指针无关(而且std::mem_fn
无论如何也无济于事)。关键是,如果你使用decltype
,那么它就可以工作。【参考方案2】:
std::declval
是一个仅声明(未定义)的函数模板。因此,它只能用于未评估的上下文中,例如 sizeof
和 decltype
的参数。它被声明为返回指定类型的右值。这允许您使用它为decltype
表达式中的函数调用制造一个虚拟参数。
例如
typedef decltype(fn(std::declval<Arg>())) t;
将t
声明为使用Arg
类型的右值调用fn
的结果类型。这类似于您的情况 (c) (fn(Arg())
),但它不需要任何 Arg
,因此它适用于没有默认构造函数的类型。
如果您的返回表达式使用foo
类型的局部变量,那么您可以再次使用decltype(fn(std::declval<foo>()))
,无论您如何构造foo
。
如果您需要左值,例如命名对象或左值引用,则可以使用std::declval<foo&>()
。这允许您处理类型取决于您是左值还是右值的情况。
【讨论】:
std::declval<Arg&>()
也是需要考虑的,尤其是考虑到您提到了一个局部变量。【参考方案3】:
这是我自己的解决方案,我能得到的最好的
template <typename Arg, typename Fn>
typename std::result_of<Fn(Arg)>::type ComputeSomething(Arg arg, Fn fn)
【讨论】:
【参考方案4】:要使 (c) 对任何东西都有效,您需要 2 个重载。第一个如(c)所示,第二个:
template <typename Arg, typename Ret>
Ret ComputeSomething(Arg arg, std::function<Ret(Arg)> fn)
另外,正如gcc bug 54111 所示 - 返回类型的推断非常不可靠。
【讨论】:
【参考方案5】:(b) 的变体不仅适用于函数指针,应该类似于
template<typename Arg, typename Ret>
Ret ComputeSomething (Arg arg, function<auto (Arg) -> Ret> f)
【讨论】:
以上是关于推断函子返回类型的通用方法?的主要内容,如果未能解决你的问题,请参考以下文章
在Typescript中,如何在工厂(ish)函数中实例化的类上获取方法的通用返回类型