2

我最近开始为我正在开发的库添加异步支持,但遇到了一个小问题。我从这样的事情开始(稍后的完整上下文):

return executeRequest<int>(false, d, &callback, false);

那是在添加异步支持之前。我试图将其更改为:

return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);

但它编译失败。

MCVE:

#include <iostream>
#include <future>

int callback(const int& t) {
    std::cout << t << std::endl;   
    return t;
}
class RequestData {
private:
    int x;
public:
    int& getX() {
        return x;   
    }
};
class X {
    public:
        template <typename T>
        T executeRequest(bool method, RequestData& requestData,
                       std::function<T(const int&)> parser, bool write) {
            int ref = 42;
            std::cout << requestData.getX() << std::endl;
            return parser(ref);
        }
        int nonAsync() {
            // Compiles 
            RequestData d;
            return this->executeRequest<int>(false, d, &callback, false);    
        }
        std::future<int> getComments() {
            RequestData d;
            // Doesn't compile 
            return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
        }
};

int main() {
    X x;
    auto fut = x.getComments();
    std::cout << "end: " << fut.get() << std::endl;
}

它失败了:

In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1505:56: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
      typedef typename result_of<_Callable(_Args...)>::type result_type;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:1709:49: note: in instantiation of template class 'std::_Bind_simple<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>' requested here
          __state = __future_base::_S_make_async_state(std::__bind_simple(
                                                       ^
main.cpp:33:25: note: in instantiation of function template specialization 'std::async<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool), X *, bool, RequestData &, int (*)(const int &), bool>' requested here
            return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
                        ^
In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1525:50: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
        typename result_of<_Callable(_Args...)>::type
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
2 errors generated.

活生生的例子

两者之间唯一的实际区别(至少我可以明显看到)是我需要显式传递this,因为我正在引用一个成员函数

我玩了一下,发现如果我用 替换它const RequestData&,它突然被允许了。但它反而会在其他地方导致问题,因为 getter 不是 const。至少从我能找到的情况来看,我需要使它成为一个 const 函数,这对 getter 本身来说很好,但我也有一些 setter,这意味着我不能这样做。

不管怎样,我想我可以试试std::bind。我将异步调用替换为:

auto func = std::bind(&X::executeRequest<int>, this, false, d, &callback, false);
return std::async(std::launch::async, func);

而且,由于某种原因,它奏效了

在这里让我感到困惑的是,它两次使用相同的参数(如果计算非异步变体,则所有三次),并且考虑到this参数,因为我正在调用的函数是成员函数。

我挖得更深,找到了一些替代解决方案(std::thread尽管参考),使用std::ref. 我知道在引擎盖下std::async运行,所以我挖掘了文档std::thread

线程函数的参数按值移动或复制。如果需要将引用参数传递给线程函数,则必须对其进行包装(例如,使用std::refor std::cref)。 (强调我的)

这是有道理的,并解释了它失败的原因。我认为std::async也受此限制,并解释了它失败的原因。

但是,挖掘std::bind

bind 的参数被复制或移动,并且永远不会通过引用传递,除非包裹在std::refor中std::cref。(强调我的)

我不使用(或者如果我用,std::ref替换),但至少如果我理解文档正确,这两个都应该无法编译。cppreference.com 上的示例也无需编译(在 Coliru 中使用 Clang 和 C++ 17 进行了测试)。conststd::crefstd::cref

这里发生了什么?

如果重要的话,除了 coliru 环境,我最初在 Docker 中重现了这个问题,运行 Ubuntu 18.04 和 Clang 8.0.1(64 位)。在这两种情况下都针对 C++ 17 编译。

4

2 回答 2

5

标准略有不同。对于std::bind

要求:is_­constructible_­v<FD, F>true。对于每一个TiBoundArgsis_­constructible_­v<TDi, Ti>trueINVOKE(fd, w1, w2, …, wN)([func.require]) 应该是某些值的有效表达式w1, w2, ..., wN,其中N有值sizeof...(bound_­args)。如下所述,调用包装器的 cv 限定符 cvg既不是 volatile 也不是 const volatile。

返回:一个参数转发调用包装器g([func.require])。的效果g(u1, u2, …, uM)应为

INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), …, std::forward<VN>(vN))

其中v1,...vN具有特定类型。在您的情况下,重要的是对应的存储变量的d类型std::decay_t<RequestData&>RequestData. 在这种情况下,您可以轻松地executeRequest<int>使用左值调用RequestData

的要求std::async要强得多:

要求:F并且每一项都应满足Cpp17MoveConstructible要求,Ti并且Args

INVOKE(decay-copy(std::forward<F>(f)),
   decay-copy(std::forward<Args>(args))...)     // see [func.require], [thread.thread.constr]

巨大的区别是衰变复制。对于d,您会得到以下信息:

decay-copy(std::forward<RequestData&>(d))

这是对decay-copy函数的调用(仅用于说明),其返回类型为std::decay_t<RequestData&>, so RequestData,这就是编译失败的原因。


请注意,如果您使用std::ref,则行为将是未定义的,因为dmay 的生命周期在调用 之前结束executeRequest

于 2019-06-15T20:16:15.123 回答
2

让我感到困惑的是,它两次都使用相同的论点

但它不会两次转发它们。调用异步版本时,调用 callable就像通过调用

std::invoke(decay_copy(std::forward<Function>(f)), 
            decay_copy(std::forward<Args>(args))...);

争论变成了类似于临时的东西!因此,引用RequestData& requestData不能绑定到它的参数。一个 const 引用、一个右值引用或一个普通的值参数可以在这里工作(如在,绑定),但一个非 const 左值引用不能。

std::bind它的调用方式不同。它也存储副本,但是“普通存储的参数 arg 作为左值参数 [原文] 传递给可调用对象”,参数的 cv 限定是从bind对象本身派生的。由于std::bind创建了一个非常量绑定对象,因此为可调用对象提供了一个非常量左值requestData。引用愉快地绑定到那个。

于 2019-06-15T20:13:14.280 回答