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有更为详尽的描述。