forward_like_tuple
「把一个人的温暖转移到另一个的胸膛」
——陈奕迅《爱情转移》
前言
C++23语核更新太少,一直没有动力去升级,不过考虑到明年是2026年了,26年用C++23还算合理吧?C++23中,能大幅简化代码的特性,首当其冲的恐怕就是deducing this了,于是先使用该特性对代码进行了一番改造,终于不用再反复写&、const &、&&和const &&这几种成员函数了,搭配上forward_like,一个成员函数即覆盖所有情形,起初还算顺利,但是改造在tuple时却碰到了单元测试编译报错的问题,一探之下,发现forward_like并不适用于tuple的get方法。
问题
对于std::tuple,get函数的实现意图是为了将tuple中的成员的访问能力暴露出来,为了简化,这里用struct代替讨论,代码如下:
|
|
可以看出,当obj为const foo &类型时,需要暴露的lvref_m成员的类型应该是int &,如果使用forward_like实现get方法,其返回类型将是const int &,因为forward_like会把const限定符加到成员类型上,还有许多其他情形也不适用,这里不再赘述。摘取cppreference的forward_like实现,代码如下:
|
|
这个实现很符合直觉,于是我心里冒出了疑问,对于tuple只能特殊处理了吗?细看之下才发现cppreference有如下描述:
|
|
原来是我光顾着看代码了!std::forward_like确实不适用于tuple,这只是一个适用主要场景的实现,提案P2445提到了3种模型分别为merge、tuple和language,而std::forward_like使用的是merge模型。这3种模型的具体区别,摘取提案表格如下:
| Owner | Member | ‘merge’ | ’tuple' | ’language' |
|---|---|---|---|---|
& |
&& |
& |
& |
|
&& |
& |
&& |
& |
& |
const |
& |
const && |
& |
& |
const & |
& |
const & |
& |
& |
const && |
& |
const && |
& |
& |
&& |
&& |
&& |
& |
|
&& |
&& |
&& |
&& |
& |
const |
&& |
const && |
&& |
& |
const & |
&& |
const & |
& |
& |
const && |
&& |
const && |
&& |
& |
const & |
const && |
const & |
const & |
|
&& |
const & |
const && |
const & |
const & |
const |
const & |
const && |
const & |
const & |
const && |
const & |
const && |
const & |
const & |
const && |
const && |
const && |
const & |
|
&& |
const && |
const && |
const && |
const & |
const |
const && |
const && |
const && |
const & |
const && |
const && |
const && |
const && |
const & |
实现
对于tuple模型,Owner和Member值类别需要折叠,而const限定符则从成员继承。不得不说,最初看到提案给的原始代码时,我都怀疑这是一个C++23的提案,但考虑到都是为爱发电,瑕不掩瑜吧!这里给出一个更现代且更简单的forward_like_tuple的实现,代码如下:
|
|
在实践时,笔者并没有使用上面给出的4个分支的版本,而是使用的3个分支的简化版本,但为契合提案和避免误导,就不必放出来了。当然,以上代码还不严谨,缺乏约束,u的推导类型和模板类型U需要相似,即,二者去除cvref之后是相同类型,定义如下:
|
|
之所以没有直接加上约束,是因为其使用场景很窄,使用者应当清楚自己在做什么,而且约束中需要使用std::remove_cvref_t这样的trait,这种trait是使用模板类实现的,对于编译器来说,模板类实例化是要比函数重载更重的,使用deducing this简化代码的同时,尽量避免增加编译时间也至关重要。
测试
提案已经给了测试代码,避免了重复劳动,该测试代码包括了std::tuple作为对照测试,截取并删改后,完整测试代码见compiler explorer,部分代码如下:
|
|
总结
tuple的get 方法在使用deducing this和forward_like_tuple之后看上去是简化不少,可从编译器的视角来看,这只是把几个get方法函数重载变成了forward_like_tuple内部的若干个编译期分支,forward_like也是如此,而且对比forward_like和forward_like_tuple的代码实现,二者从结构上也能形成对应。本文只为抛砖,提案 P2445有更为详尽的描述。