IE盒子

搜索
查看: 113|回复: 1

现代C++学习——实现一个std::tuple

[复制链接]

3

主题

6

帖子

12

积分

新手上路

Rank: 1

积分
12
发表于 2023-1-12 04:12:03 | 显示全部楼层 |阅读模式
在C++11后的C++中,标准库为我们提供了不少的工具。


这5个工具都是利用模板完成,接下来我会写出这几个工具的简易实现。
本期先来看看tuple的实现。
头文件<tuple>
创建一个tuple
  tuple<int, char, string>t(114, '&', "hello world");
获取tuple中的值
cout << get<0>(t) << endl;
cout << get<1>(t) << endl;
cout << get<2>(t) << endl;
注意,这里 <> 内放得是常量。例如 pair的first ,second 。 这里的get<0/1/2> 可以理解为
tuple.No1 ; tuple.No2 ; tuple.No3 这样的。
来看一下如何实现
首先是定义
template<typename ...Ty>struct Tuple;
这里,我们用到了,模板的不定长参数 ( 使用C++11变长参数模板 处理任意长度、类型之参数实例
简单的理解就是 ...Ty 表示把一堆类型给打包


我们通过Ty... 就可以将其解包


template<>struct Tuple<> {};
template<typename Ty1, typename ...Ty2>
struct Tuple<Ty1, Ty2...> : Tuple<Ty2...> {
    Ty1 val;
};
这样当我们定义这样类型时
Tuple<int, char, string>
最终会产生这些类型
template<>
struct Tuple<>
{

};
template<>
struct Tuple<std::basic_string<char> > : public Tuple<>
{
    std::basic_string<char> val;
};
template<>
struct Tuple<char, std::basic_string<char> > : public Tuple<std::basic_string<char> >
{
    char val;
};
template<>
struct Tuple<int, char, std::basic_string<char> > : public Tuple<char, std::basic_string<char> >
{
    int val;
};
根据这个继承关系,我们可以写出其内存结构


添加构造函数
template<typename Ty1, typename ...Ty2>
struct Tuple<Ty1, Ty2...> : Tuple<Ty2...> {
    Ty1 val;
    using Base = Tuple<Ty2...>;
    Tuple() {}
    Tuple(Ty1 v, Ty2... args) : val(v), Base(args...) {}
};
此时就能使用这样的创建了
Tuple<int, int, string> t(114, 514, "1919810");
问题是如何访问数据呢?
我们可以加上这样的一个函数
Base& getBase() {
    return *this;
}
可以这样访问数据
cout << t.val << endl;
cout << t.getBase().val << endl;
cout << t.getBase().getBase().val << endl;
就是通过一个类型转换,让编译器把<int,char,string>的一块内存,看成<char,string>


不过这样的访问着实有点不优雅,我们希望可以实现一个get
其实做法和上面的类似,例如<int,int,char,string> 当我想要get<2> 的时候,就去找到<char,string>的类型,然后类型转换一下。
对此,你可能需要一点点模板元编程的知识。
严格鸽:现代C++学习 模板元编程入门
template<int idx, typename _Tuple>
struct Tuple_element {
    using Type = typename Tuple_element<idx - 1, typename _Tuple::Base>::Type;
};
template<typename _Tuple >
struct Tuple_element<0, _Tuple> {
    using Type = _Tuple;
};
使用效果


这样就可以写出自己的get了
template<int idx, typename _Tuple>
constexpr auto& Get(_Tuple& t) {
    using Type = typename Tuple_element < idx, _Tuple>::Type;
    return static_cast<Type&>(t).val;
}
使用
Tuple<int, int, char, string>x(114, 514, 'a', "soul");
Get<0>(x) = 1919;
Get<1>(x) = 810;
cout << Get<0>(x) << Get<1>(x) << endl;
cout << Get<2>(x) << Get<3>(x) << endl;
最后我们来实现一个判断是否相等。
首先,比较两个元组,两个元组的参数数量应该是一样的
template<typename ...Ty1, typename ...Ty2>
bool operator == (const Tuple<Ty1...>& L, const Tuple<Ty2...>& R) {
    if (sizeof...(Ty1) != sizeof...(Ty2))return false;
这里的sizeof 就是 计算不定长参数的个数。不过这里其实,如果参数数量对不上,根本就过不了下面的编译。
所以直接这样
template<typename ...Ty1, typename ...Ty2>
bool operator == (const Tuple<Ty1...>& L, const Tuple<Ty2...>& R) {
    return L.equal(R);
}
并添加这个函数
template <class... Ty>
bool equal(const Tuple<Ty...>& rhs)const {
    return this->val == rhs.val && Base::equal(rhs.getBase());
}
做法就是不断的递归,当然,一开始的空基类也要写个函数
template <class... Ty>
bool equal(const Tuple<>& rhs)const {
    return true;
}
这样判断就完成了
Tuple<int, int, char, string>x(114, 514, 'a', "soul");
Tuple<int, int, char, string>y(114, 514, 'a', "soul");
cout << (x == y) << endl;
Get<3>(y) = '4';
cout << (x == y) << endl;
这样一个简易的tuple就完成了,放上完整代码,与一些测试例。
template<typename ...Ty>struct Tuple;
template<>struct Tuple<> {
    template <class... Ty>
    bool equal(const Tuple<>& rhs)const {
        return true;
    }
};
template<typename Ty1, typename ...Ty2>
struct Tuple<Ty1, Ty2...> : Tuple<Ty2...> {
    Ty1 val;
    using Base = Tuple<Ty2...>;
    Tuple() {}
    Tuple(Ty1 v, Ty2... args) : val(v), Base(args...) {}
    Base& getBase() {
        return *this;
    }
    const Base& getBase() const {
        return *this;
    }
    template <class... Ty>
    bool equal(const Tuple<Ty...>& rhs)const {
        return this->val == rhs.val && Base::equal(rhs.getBase());
    }
};

template<int idx, typename _Tuple>
struct Tuple_element {
    using Type = typename Tuple_element<idx - 1, typename _Tuple::Base>::Type;
};
template<typename _Tuple >
struct Tuple_element<0, _Tuple> {
    using Type = _Tuple;
};

template<int idx, typename _Tuple>
constexpr auto& Get(_Tuple& t) {
    using Type = typename Tuple_element < idx, _Tuple>::Type;
    return static_cast<Type&>(t).val;
}

template<typename ...Ty1, typename ...Ty2>
bool operator == (const Tuple<Ty1...>& L, const Tuple<Ty2...>& R) {
    return L.equal(R);
}

int main()
{
    Tuple<int, string> t(21, "ygg");
    vector<Tuple<int, int, double>>vec;
    for (int i = 1; i <= 10; i++) {
        vec.push_back({ i, i, 1.0 / i });
    }
    for (auto& x : vec) {
        cout << Get<0>(x) << " " << Get<1>(x) << " " << Get<2>(x) << endl;
    }
}
一些细节,比较拷贝构造,移动构造什么的,大家自己完善吧。
回复

使用道具 举报

1

主题

5

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2023-1-12 04:12:36 | 显示全部楼层
精彩[赞同]
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表