读有关 C++ co_await 的文章时遇到的一些问题

最近在读 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; } 不就好了么?

没毛病。

  1. Awaitable&&, 完美转发参数。get_awaiter(std::forward<T>(T arg) ) 调用过来的.
  2. 成员 obj.operator co_await () 调用。
  3. 非成员 operator co_await (obj) 调用。
  4. if-elif-else, 第一分支 lvalue copy,第二分支 rvalue 引用, 第三分支,default case.
1 个赞

第一分枝有重载的成员operator co_await,第二分枝有重载的外部co_await,默认分枝没有任何重载的co_await操作符(重载co_await不是必须的,通常都没有)

1 个赞

我也是这个意思。我没写清楚吧。2. 3. operator 成员/非成员 overload

感谢二位,茅塞顿开。。。我第一眼看到 ‘has’ 和 ‘non’ 的组合脑子里想到的是 “doesn’t have”,然后就没再细瞧这个短语。。。


所以第一个分支的要求其实是,Awaitable 的 operator co_await() 不管是 & 还是 && 限定都必须能调用么? 因为我看它这边就直接写了个 <Awaitable>


这边为啥会涉及到 copy? 我感觉只有引用。


这边相当于是 std::forward 吧?不一定是 rvalue。

是,1. 没有 copy. 2. 不一定是 rvalue.

看你怎么 get_awaiter 调用 awaitable 参数去 forwarding reference。

static_cast<Awaitable&&>(awaitable)

rvalue → rvalue reference. lvalue → lvalue reference.

  1. 这里没涉及到 std::forward, 上面有误。

  2. 还没读过 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 个赞

他这边是在用引用折叠吧我感觉

  • Obtaining the Awaiter section: why using static_cast<T&&> instead of std::foward<T> in this section’s code?
    获取 Awaiter 部分:为什么在这个部分的代码中使用 static_cast<T&&> 而不是 std::foward<T>

These two are equivalent.
I sometimes write static_cast<T&&> to reduce compile-times as it doesn’t need to instantiate a function template.
这两者等价。我有时会写 static_cast<T&&> 来减少编译时间,因为它不需要实例化一个函数模板。

对,我又看了下楼主链接的原文,这里的static_cast等价于std::forward而不是move。楼主在引用时忽略了函数前的模版声明

template<typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable)

有没有这个template,决定了这里是forward还是move

1 个赞