最近在读 Lewis Baker 关于 C++ coroutine 的文章:
https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await
遇到了些比较迷惑的地方,就去 GitHub Issues 上问问题,但是作者似乎很久不活跃。所以想请教一下坛里熟悉 C++ coroutine 的道友。
文章里提到 co_await
某个 awaitable 对象时,获取 awaiter
的行为可以翻译成如下代码:
template <typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable) {
if constexpr (has_member_operator_co_await_v<Awaitable>)
/* ^^^^^^^^^ */
return static_cast<Awaitable&&>(awaitable).operator co_await();
else if constexpr (has_non_member_operator_co_await_v<Awaitable&&>)
/* ^^^^^^^^^^^ */
return operator co_await(static_cast<Awaitable&&>(awaitable));
else
return static_cast<Awaitable&&>(awaitable);
}
这有三个问题:
has_[non_]member_operator_co_await_v<Awaitable>
和 has_[non_]member_operator_co_await_v<Awaitable&&>
有什么不同吗?我在代码中注释了出来。
不是很懂 operator co_await(<expr>)
是啥意思。C++ 里 std::vector{1,2,3}[0]
和 std::vector{1,2,3}.operator[](0)
等价,所以我感觉这里 awaitable.operator co_await()
和 operator co_await(awaitable)
不是一个意思么?
if-elif-else
的最后一个分支应该是不可达吧?感觉作者这里写错了。
文章还提到
Then, if the Awaitable object, awaitable
, has an applicable operator co_await()
overload then this is called to obtain the Awaiter object.
Otherwise the object, awaitable
, is used as the awaiter object.
我怀疑第二条规则存在的意义。
如果想让 awaitable
被当成 awaiter 使用, 直接定义 auto& awaitable::operator co_await() { return *this; }
不就好了么?
第一分枝有重载的成员operator co_await,第二分枝有重载的外部co_await,默认分枝没有任何重载的co_await操作符(重载co_await不是必须的,通常都没有)
1 个赞
我也是这个意思。我没写清楚吧。2. 3. operator 成员/非成员 overload
finalpatch:
第二分枝有重载的外部co_await
感谢二位,茅塞顿开。。。我第一眼看到 ‘has’ 和 ‘non’ 的组合脑子里想到的是 “doesn’t have”,然后就没再细瞧这个短语。。。
所以第一个分支的要求其实是,Awaitable 的 operator co_await()
不管是 &
还是 &&
限定都必须能调用么?
因为我看它这边就直接写了个 <Awaitable>
。
yssource:
第一分支 lvalue copy
这边为啥会涉及到 copy?
我感觉只有引用。
yssource:
第二分支 rvalue 引用
这边相当于是 std::forward 吧?不一定是 rvalue。
是,1. 没有 copy. 2. 不一定是 rvalue.
看你怎么 get_awaiter 调用 awaitable 参数去 forwarding reference。
static_cast<Awaitable&&>(awaitable)
rvalue → rvalue reference. lvalue → lvalue reference.
这里没涉及到 std::forward, 上面有误。
还没读过 co_await 源码,但写了个简单的例子试试,不一定对。
#+BEGIN_SRC C++ :results output :flags -std=c++20
#include <type_traits>
#include <typeinfo>
#include <iostream>
namespace MyCo {
// Helper traits to detect member and non-member operator_co_await
template <typename T, typename = void>
struct has_member_operator_co_await_t : std::false_type {};
template <typename T>
struct has_member_operator_co_await_t<T, std::void_t<decltype(std::declval<T>().get_awaiter())>> : std::true_type {};
template <typename T>
inline constexpr bool has_member_operator_co_await_v = has_member_operator_co_await_t<T>::value;
template <typename T, typename = void>
struct has_non_member_operator_co_await_t : std::false_type {};
template <typename T>
struct has_non_member_operator_co_await_t<T, std::void_t<decltype(get_awaiter(std::declval<T>()))>> : std::true_type {};
template <typename T>
inline constexpr bool has_non_member_operator_co_await_v = has_non_member_operator_co_await_t<T>::value;
// Example usage
struct MemberOperatorAwaitable {
int value;
inline MemberOperatorAwaitable operator co_await() const { return {value}; }
};
struct NonMemberOperatorAwaitable { int value; };
inline NonMemberOperatorAwaitable operator co_await(NonMemberOperatorAwaitable&& obj) {
return {obj.value};
}
struct WithoutOperatorAwaitable{ int value; };
// The get_awaiter function
template <typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable) {
if constexpr (has_member_operator_co_await_v<Awaitable>)
return static_cast<Awaitable&&>(awaitable).operator co_await();
else if constexpr (has_non_member_operator_co_await_v<Awaitable&&>)
return operator co_await(static_cast<Awaitable&&>(awaitable));
else
return static_cast<Awaitable&&>(awaitable);
}
} // MyCo namespace end.
int main() {
using namespace MyCo;
MemberOperatorAwaitable mo_awaitable{100};
NonMemberOperatorAwaitable nmo_awaitable{-100};
WithoutOperatorAwaitable woo_awaitable{0};
auto rst1 = get_awaiter(mo_awaitable);
auto rst11 = get_awaiter(std::move(mo_awaitable));
auto rst2 = get_awaiter(nmo_awaitable);
auto rst22 = get_awaiter(std::move(nmo_awaitable));
auto rst3 = get_awaiter(woo_awaitable);
auto rst33 = get_awaiter(std::move(woo_awaitable));
std::cout << typeid(rst1).name() << " : " << rst1.value << "\n";
std::cout << typeid(rst11).name() << " : " << rst11.value << "\n";
std::cout << typeid(rst2).name() << " : " << rst2.value << "\n";
std::cout << typeid(rst22).name() << " : " << rst22.value << "\n";
std::cout << typeid(rst3).name() << " : " << rst3.value << "\n";
std::cout << typeid(rst33).name() << " : " << rst33.value << "\n";
return 0;
}
#+END_SRC
#+RESULTS:
: N4MyCo23MemberOperatorAwaitableE : 100
: N4MyCo23MemberOperatorAwaitableE : 100
: N4MyCo26NonMemberOperatorAwaitableE : -100
: N4MyCo26NonMemberOperatorAwaitableE : -100
: N4MyCo24WithoutOperatorAwaitableE : 0
: N4MyCo24WithoutOperatorAwaitableE : 0
楼主贴的片段不涉及perfect forwarding以及std::forward。这里纯粹就是检测是否存在重载的operator co_await,如果有就调用它们。所有的参数都是&&也就是按右值引用接收,然后在使用时static_cast<&&>强制转型为右值引用。之所以需要这个转型是因为右值引用参数变量本身是左值(我知道这点比较怪,所以只要记住就好)。这个static_cast实质上等效于std::move(std::move的实现就是这个static_cast)。
1 个赞
对,我又看了下楼主链接的原文,这里的static_cast等价于std::forward而不是move。楼主在引用时忽略了函数前的模版声明
template<typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable)
有没有这个template,决定了这里是forward还是move
1 个赞