std::tuple
的原理并不复杂,但有些细节非常有意思。其中有一个是至少在gnu C++ std
的实现中,std::tuple
是倒序存储的:
std::tuple<int, int> ii;
std::cout << (&std::get<0>(ii)) < (&std::get<1>(ii)) << std::endl; // output false
很多人都以为它就是下面这样简单实现的:
template<typename Head, ...typename Tail>
my::tuple : private my::tuple<...Tail> {
Head head;
};
但这个实现有个问题,空类也会占用一个字节的空间!这和std::tuple
的实际表现不符。具体可以参考下面代码:
struct Empty { };
int main() {
std::cout << sizeof(std::tuple<double, Empty>) << std::endl; // 8
std::cout << sizeof(my::tuple<double, Empty>) << std::endl; // 16
}
为了达到省去空类的占用空间,std::tuple
的所有成员都是继承出来的。这利用了 C++的一个特性:继承空类不会增加子类的空间。
template<std::size_t _Idx, typename _Head, typename... _Tail>
struct _Tuple_impl<_Idx, _Head, _Tail...>
: public _Tuple_impl<_Idx + 1, _Tail...>, private _Head_base<_Idx, _Head>
{ }
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>
{ }
也就是说std::tuple<int, double, char>
基本相当于:
std::tuple<int, double, char>
: private _Head_base<2, char>, private _Head_base<1, double>, private _Head_base<0, int>
{}
而_Head_base
只是对元素的一个简单封装:
template<std::size_t _Idx, typename _Head>
struct _Head_base<_Idx, _Head, false> {
_Head value;
};
但它对空类做了一个特殊处理,使得封装后还是空类(注意上面的实现并没有得到空类):
template<std::size_t _Idx, typename _Head, bool = __empty_not_final<_Head>::value>
struct _Head_base;
// 此时 _Head 为空类。
template<std::size_t _Idx, typename _Head>
struct _Head_base<_Idx, _Head, true> : public _Head
{ }
由此可见,std::tuple
的确是倒序存储的。但为什么它要这么做呢?【待续】
Q. E. D.