推断函子返回类型的通用方法?

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>)

其中&lt;ret-expr&gt;是任意表达式,其中涉及arg,我应该使用&lt;decl-expr&gt;来设置ComputeSomething的返回类型等于函子的返回类型。

函子可以是类、lambda 或函数指针。

目前我找到的部分解决方案。

(a) 我的链接问题的答案由 ecatmur 完成。本质上,它在&lt;decl-expr&gt; 中重复返回语句。问题:容易出错,如果包含局部变量则无法工作。

(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 痛苦。如果您实际上只是转发值,您仍然需要记住添加右值引用限定符 (&amp;&amp;)。

我认为重要的其他事情:您的函数将参数转发给另一个函数。在这种情况下,您应该始终使用右值引用来传递参数。

如果您想要做的只是提高可维护性:在 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 是一个仅声明(未定义)的函数模板。因此,它只能用于未评估的上下文中,例如 sizeofdecltype 的参数。它被声明为返回指定类型的右值。这允许您使用它为decltype 表达式中的函数调用制造一个虚拟参数。

例如

typedef decltype(fn(std::declval<Arg>())) t;

t 声明为使用Arg 类型的右值调用fn 的结果类型。这类似于您的情况 (c) (fn(Arg())),但它不需要任何 Arg,因此它适用于没有默认构造函数的类型。

如果您的返回表达式使用foo 类型的局部变量,那么您可以再次使用decltype(fn(std::declval&lt;foo&gt;())),无论您如何构造foo

如果您需要左值,例如命名对象或左值引用,则可以使用std::declval&lt;foo&amp;&gt;()。这允许您处理类型取决于您是左值还是右值的情况。

【讨论】:

std::declval&lt;Arg&amp;&gt;() 也是需要考虑的,尤其是考虑到您提到了一个局部变量。【参考方案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)函数中实例化的类上获取方法的通用返回类型

如何使通用可变参数函数中先前声明的函数的返回类型成功进行上下文推断?

打字稿不推断通用对象的值

快速返回特殊类型的通用协议的方法

使用联合类型进行类型推断 - 不存在最佳通用类型