【问题标题】:Using `std::function<void(...)>` to call non-void function使用 `std::function<void(...)>` 调用非 void 函数
【发布时间】:2012-03-09 12:52:16
【问题描述】:

前段时间我使用std::function 几乎是这样的:

std::function<void(int)> func = [](int i) -> int { return i; };

基本上,我这样做是因为我想在std::function 中存储不同的函数对象,但我不想限制这些函数的返回类型。由于这似乎有效,我就同意了。但我不相信它可以安全使用,我也找不到任何关于它的文档。有谁知道这种用法是否合法?或者更一般地说,可以安全地分配给std::function 的对象的规则是什么?

编辑

为了澄清,我关心的问题是 lambda 函数返回 int,而 func 声明为返回类型 void。我不确定这是否可以,尤其是在拨打了func() 之后。

【问题讨论】:

    标签: c++ c++11 return-type std-function


    【解决方案1】:

    您的代码具有未定义的行为。它可能会或可能不会按您的预期工作。它具有未定义行为的原因是因为 20.8.11.2.1 [func.wrap.func.con]/p7:

    要求: F 应为 CopyConstructiblef 对于参数类型 ArgTypes 和返回类型 R 应该是可调用的 (20.8.11.2)。

    要使f 可调用返回类型Rf 必须返回可隐式转换为std::function 的返回类型的内容(在您的情况下为void)。并且int 不能隐式转换为void

    我希望您的代码适用于大多数实现。但是在至少一种实现 (libc++) 上,它无法编译:

    test.cpp:7:30: error: no viable conversion from 'int (int)' to 'std::function<void (int)>'
        std::function<void(int)> ff = f;
                                 ^    ~
    

    具有讽刺意味的是,这种行为的基本原理源于another SO question

    另一个问题提出了std::function 使用的问题。该问题的解决方案涉及让实现在编译时强制执行 Requires: 子句。相比之下,这个问题的解决方案是禁止实施执行 Requires: 子句。

    【讨论】:

    • @LucDanton:我没有查看boost::function 文档。我认为这与这个问题无关。我只看了 N3290,它是被选为 C++11 标准的论文。另一个 SO 问题与实现在编译时强制执行 Requires: 子句有关。此类强制执行不是必需的,但符合要求。
    • 没有。另一个问题提出了std::function 使用的问题。该问题的解决方案涉及让实现在编译时强制执行 Requires: 子句。相比之下,这个问题的解决方案是禁止实现执行 Requires: 子句。
    • 从理论上来说,这个还是比较容易支持的。要么提供 void 返回类型的部分特化,并在 __callable 特征中排除 is_convertible&lt;..., _Rp&gt; 检查 + 忽略 operator() 中的返回值,或者(可能更好),为可转换检查添加另一个级别的间接,专门针对void 返回类型,并通过maybe_swallow 函数(如here)调用包装对象。因为它只是一个参考实现,我很懒,只支持函数指针。 :)
    • 但是,当函数在std::function 上重载时,必须注意模棱两可的警告,除了std::function 的返回类型之外,两个重载都是相同的。请参阅提供的链接中的评论。
    • @Yakk:你是对的。更新这个答案以详细说明规则何时以及如何演变对我来说不是一个坏主意。
    【解决方案2】:

    您的用例已根据标准进行了良好定义。

    你正在从一个可调用对象[1]构造一个std::function

    §20.8.11.2.1/7:

    template<class F> function(F f);
    

    要求:F 应为 CopyConstructible。对于参数类型 ArgTypes,f 应为 Callable (20.8.11.2) 并返回类型 R。

    那么你的 f 是可调用的吗?

    §20.8.11.2/2 说:

    F 类型的可调用对象 f 对于参数类型 ArgTypes 是可调用的 如果表达式INVOKE (f, declval&lt;ArgTypes&gt;()..., R),则返回类型 R,被视为未计算的操作数 (第 5 条),格式正确 (20.8.2)。

    INVOKE的定义说:

    §20.8.2

    1. 如下定义INVOKE (f, t1, t2, ..., tN): ...处理成员函数/ var指针的东西... — f(t1, t2, ..., tN) 在所有其他情况下。

    2. 定义INVOKE (f, t1, t2, ..., tN, R) as INVOKE (f, t1, t2, ..., tN)隐式转换为R

    而且由于任何类型都可以隐式转换为 void,因此您的代码应该可以使用符合标准的编译器。 正如下面的 litb 所指出的,没有隐式转换为void 所以这不是很好的定义。

    [1]:我认为 lambda 在这里算作可调用对象,尽管我没有对此的参考。您的 lambda 也可以用作函数指针,因为它不捕获上下文

    【讨论】:

    • 没有隐式转换为 void。
    • @JohannesSchaub-litb 那么是原始代码错误,还是我只是使用标准的错误部分来证明它是正确的?
    • @je4d:哇,感谢您的参考。但看起来 Johannes 是对的——唯一阻止这一点正确的是我们不能隐式转换为 void。看起来我必须将函数对象包装在一个 void 返回函数中。
    【解决方案3】:

    这对于匿名函数来说似乎没问题。

    来自http://www.alorelang.org/release/0.5/doc/std_function.html 的引用(这不是来自 C++ 标准库,但看起来他们在绑定到 C++ 时使用了类似的东西)

    函数对象只能通过函数定义、匿名函数表达式或使用点 (.) 运算符访问绑定方法来创建。

    另一种可能的方法是将函数指针存储在 auto 中,如下所示:http://en.wikipedia.org/wiki/Anonymous_function(C++ 部分)

    【讨论】:

    • 感谢您的回答。但问题实际上是关于返回类型的不匹配。我的 lambda 返回一个 int,但它存储在一个 std::function 中,其中 void 作为返回类型。
    猜你喜欢
    • 2023-02-07
    • 2014-12-11
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 2019-02-20
    • 2015-06-08
    相关资源
    最近更新 更多