【问题标题】:On the various ways of getting the result type of a function关于获取函数结果类型的各种方法
【发布时间】:2016-06-20 10:19:00
【问题描述】:

在必须推断函数调用结果类型的上下文中,C++ 似乎更乐意帮助我们,提供(至少据我所知以下)两种解决方案:

  • result of 类型特征:

    std::result_of<F(Args...)>::type
    
  • 核心语言语法:

    decltype(std::declval<F>()(std::declval<Args>()...); 
    

我的问题是,两者之间有什么区别?是否存在一个不能被另一个替代的上下文,如果不是,为什么我们需要一个类型特征来做一些语言可以开箱即用的事情?

【问题讨论】:

  • 其实,我不确定这是不是骗子。 That one 通常是关于 decltype。这一个专门在函数调用的上下文中。

标签: c++ c++14 typetraits


【解决方案1】:

有三个不同之处。

  1. 最初,std::result_of 不需要对 SFINAE 友好。因此,如果要在上下文中使用它来验证 F 是否可以用 Args... 调用,它会给你一个硬错误,而 decltypestd::declval 只会导致可能的预期替换失败。 N3462 修复了规范以使其对 SFINAE 友好。

    不过,在兼容的 C++14 编译器上,两者都对 SFINAE 友好。 std::result_of_t&lt;Fn(ArgsTypes...)&gt; 实际上是根据后者定义的。来自[meta.trans.other]:

    如果表达式 INVOKE (declval&lt;Fn&gt;(), declval&lt;ArgTypes&gt;()...) 很好 当被视为一个 未计算的操作数(第 5 条), 成员 typedef 类型应命名为 输入decltype(INVOKE (declval&lt;Fn&gt;(), declval&lt;ArgTypes&gt;()...)); 否则,将没有会员 输入。

  2. 正如result_of 定义的语言中所明确指出的,Fn 类型可以是任何INVOKE-able,而显式调用std::declval&lt;F&gt;() 仅适用于函数和函数对象。所以如果你有:

    using F = decltype(&X::bar);
    using T1 = std::result_of_t<F(X*)>;                        // ok
    using T2 = decltype(std::declval<F>()(std::declval<X*>()); // not ok
    
  3. 对于某些类型,std::result_of 实际上不起作用,因为指定某些 type-id 是非法的(请参阅 my related question)。这些类型对F 使用函数(而不是对函数的指针/引用)或对任何Args... 使用抽象类。所以考虑一些看似无害的事情,比如:

    template <class F, class R = std::result_of_t<F()>>
    R call(F& f) { return f(); }
    
    int foo();
    call(foo); // error, unresolved overload etc.
    

    这会导致 SFINAE 失败(至少它不是硬错误),因为我们必须形成 int()() 类型 - 一个返回函数的空函数。现在,从技术上讲,我们错误地编写了代码。应该是:

    template <class F, class R = std::result_of_t<F&()>>
    R call_fixed(F& f) { return f(); }
    

    这行得通。但是如果我们在 declval 上犯了同样的错误,我们会没事的:

    template <class F, class R = decltype(std::declval<F>()())>
    R call_declval(F& f) { return f(); }
    

【讨论】:

  • 第三个:如果FArgs之一是抽象的,或者F是函数类型,result_of&lt;F(Args...)&gt;无效,但decltype/declval有效正确。 (修复方法是使用result_of&lt;F&amp;&amp;(Args&amp;&amp;...)&gt;
  • @Barry 我相信形成类型“函数返回函数”或“函数[按值获取抽象类型]按值返回抽象类型”是不可能的。
  • @Columbo 太不幸了。
  • @Barry 也许result_of 是在可变参数模板出现之前引入的(最初是在2003 中提出的)。为了传递result_of 所需的信息,他们认为使用复合函数类型既高效(与某些元组式类模板相反)又具有简洁的语法。后者仍然成立,IMO 胜过那些极端情况。
  • @Columbo 是的,不过我并不完全理解。 Care to elaborate?
【解决方案2】:

result_of 在应用于给定类型时产生std::invoke 的返回类型——它甚至用于invoke 声明的返回类型。但是,invoke 涵盖的场景远不止普通的非成员函数调用。您展示的decltype(…) 中的表达式只是result_of 检查的众多表达式之一。

因此,只有当F 保证为函数对象类型时,您的选项才等效。否则result_of&lt;…&gt; 可能在decltype(…) 无效的情况下有效:

struct A {int i;};
decltype(std::declval<int A::*>()(std::declval<A>())) // error
std::result_of_t<int A::*(A)> // = int&&, treated as a member access

Demo。 (请注意,result_of 从 C++14 开始对 SFINAE 友好,即无效类型会导致与 decltype 一样的平滑推导失败。)

【讨论】:

    【解决方案3】:

    请一些不是很有经验的 C++ 程序员,让他们猜一猜 sn-ps 的每个代码的作用。

    有多少人获得了第一名?有多少人答对了?

    给这个操作起一个名字使它更具可读性。

    现在请一些稍微有经验的 C++ 程序员,让他们在使用和不使用 result_of 的情况下获取函数调用的结果类型。有多少人做对了?需要多长时间?

    这已经是非常重要的元编程了,将其封装起来更容易被 Google 发现,也更容易做对。

    另外,第一个版本要短一些。

    因此 result_of 非常有用,而且由于标准库本身需要它很多次,因此将它提供给程序员也很简单。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-23
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多