Intellisense and Copy elision

最近开发 co_context,遇到了很多 C++ 周边的坑。其中一个坑是 intellisense 翻脸不认 C++17 标准的强制拷贝消除(Mandatory elision of copy/move operations)。

这个 intellisense 来自 Vscode Extention: C/C++ (Microsoft)。

Brief Solution

1
2
3
4
5
#ifdef __INTELLISENSE__
// The fake code to cheat intellisense...
#else
// The real code for compiler...
#endif

Background

co_context 要开发一个 lock_guard,但用户必须要写 co_await,导致只能写成以下形式:

1
auto lk = co_await mtx.lock_guard();

lk 在析构的时候会自动调用 mtx.unlock()。这看起来很美好,但这里发生了 Copy elision,根据 C++17 标准,这是强制编译器执行的优化,无论拷贝/移动构造函数有没有副作用。为了防止用户误用,我干脆删除了拷贝/移动构造函数,注意这也是符合标准的做法。

但问题是,intellisense 不知道 Copy elision!因为我删除了那些构造函数,导致 intellisense 会报错:「无法引用 xx 构造函数,它已经被删除」。

最糟糕的是,库的用户在正确使用 lock_guard 时,都可能收到这一条错误的报错(哪怕编译通过),这很有迷惑性,显然会大大降低用户体验。

One way: compromise

最初我选择向 intellisense 妥协:你想让我写移动构造,我写就是了。为了语义正确性,我不得不将 lock_guard 类内部的 mutex& 改成 mutex*,以支持「移动」。

最后在 ~lock_guard() 内,我不得不判断 if (mtx != nullptr) mtx->unlock(),这很丑,也影响了性能。我不能忍受。

The second way: cheat

错误的根源是 intellisense 不知道 Copy elision。错误的代价不应由我来承担。

我为 lock_guard 增加了移动构造函数,但标注了 [[deprecated]],而且内部是 assert(false),这是为了防止用户误用。这成功地骗过了 intellisense。

因为保证不会拷贝/移动,所以可以删除 if (mtx != nullptr) 这样的愚蠢代码啦!

The final solution: #ifdef

经过高人指点,欺骗 intellisense 有专门的宏来完成。所以最后的解决办法是:

1
2
3
4
5
6
7
8
9
10
11
12
#ifdef __INTELLISENSE__
[[deprecated(
"This function is for cheating intellisense, "
"who doesn't sense RVO. "
"You should NEVER use this explicitly or implicitly.")]]
lock_guard(lock_guard && other) noexcept
: mtx(other.mtx) {
assert(false && "Mandatory copy elision failed!");
};
#else
lock_guard(lock_guard && other) = delete;
#endif

其中,[[deprecated]]assert 可以删去,因为编译器永远不会处理它们。