C++ 模板简介(三)—— Tuple

本文详细介绍了C++中Tuple的使用方法,包括如何通过std::tuple构造复合类型以实现多返回值函数,以及如何通过get函数和C++17特性访问Tuple内部值。此外,还探讨了Tuple的两种实现方式,一种是通过递归访问,另一种是通过继承机制避免递归,实现零开销的元素访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Tuple

导入

我们可以通过 std::tuple 构造一个复合类型,比如可以使得函数拥有“多个”返回值:

std::tuple<int, int> divide(int a, int b) {
    return make_tuple(a / b, a % b);
}

然后我们可以通过 get<> 函数来获得 tuple 内存储的值:

auto result = divide(4, 3);
int quotient = std::get<0>(result); // 获取 tuple 的第 0 个数
int remainder = std::get<1>(result); // 获取 tuple 的第 1 个数

还可以:

int quotient, remainder;
std::tie(quotient, remainder) = divide(4, 3);

或者在 C++17 中:

auto [ quotient, remainder ] = divide(4, 3);

是不是更有多返回值的感觉?

实现

首先我们要知道我们如何实现一个 tuple。借鉴之前实现一系列模板元编程得到的基本函数的思想,我们只令 tuple<head, tail...> 这个类型只存 head 类型的变量,剩余类型的变量在 tuple<tail...> 中保存。那么如何实现这样的分开保存呢?一种方法是在 tuple 类内存两个变量,一个是 head 类型的变量即当前值,第二个是 tuple<tail...> 类型的变量存剩余值。这样我们在 get 的时候就直接递归拿值即可(比较朴素的方法)。但是这样的话 get 函数的性能就会下降,因为在我们只需要特定的值的情况下, get 却可能需要递归比较多层,而get函数本来要获取的值的下标识编译期确定的,我们却在运行期做了遍历 tuple 的工作,不符合我们的零开销策略(不过加上 constexpr 之后这种方法的开销就降为零了)。

接下来我们介绍另一种方法实现,就是继承,如果我们存在 tuple<Args...>,我们要找特定的值,遍历一次其祖先,和第一种方法一样。为了能够避免遍历,我们在模板参数中添加一个 N 表示当前的部分 tuple 是原 tuple 的第几个元素开始的,新的类我们称作 tuple_impl。那么 tuple_impl<N, head, tail...> 的父类就理所当然的是 tuple_impl<N + 1, tail...>,即父类是下一个元素开始的子 tuple。那么这么实现有什么好处?我们在实现 get 函数的时候就可以这么写:

template <std::size_t N, typename _Head, typename... _Tail>
constexpr const _Head &get_helper(const tuple_impl<N, _Head, _Tail...> &t) {
    return tuple_impl<N, _Head, _Tail...>::head(t);
}

template <std::size_t N, typename... Args>
constexpr const tuple_element_t<N, tuple<Args...>> &
get(const tuple<Args...> &t) {
    return get_helper<N>(t);
}

你可以看到,get函数调用了get_helper<N>函数,tuple<Args...>类是tuple_impl<0, Args...> 的子类,t 传入 get_helper<N> 后变成了 tuple_impl<N, Args2...>,而这个 Args2 是我们交由编译器自动推导的(get_helper<N> 的参数我们只给了 N 而没有给 _Head_Tail)。也就是说我们利用了继承的机制后,我们可以直接拿到 tuple<Args...> 的第 N 个父类 tuple_impl<N, Args2...>!从而直接获得第 N 个元素,从而避免了递归调用(而且由于我们没有虚函数和虚继承,所以这个是零开销的)。

下面是完整的 tuple 实现,完成了大部分功能(实现了tieget)。

template <size_t N, typename... _Elements>
struct tuple_impl;

template <std::size_t N, typename _Head>
struct tuple_impl<N, _Head> {

    explicit tuple_impl(const _Head &head) : value(head) {}

    template <typename _UHead>
    void assign(const tuple_impl<N, _UHead> &in) {
        head(*this) = tuple_impl<N, _UHead>::head(in);
    }

    static constexpr _Head &head(tuple_impl &t) {
        return t.value;
    }

    static constexpr const _Head &head(const tuple_impl &t) {
        return t.value;
    }

    _Head value;
};

template <std::size_t N, typename _Head, typename... _Tail>
struct tuple_impl<N, _Head, _Tail...>
    : public tuple_impl<N + 1, _Tail...> {

    typedef tuple_impl<N + 1, _Tail...> base_type;

    explicit tuple_impl(const _Head &head, const _Tail &... tail)
        : base_type(tail...), value(head) {}
    
    template <typename... _UElements>
    void assign(const tuple_impl<N, _UElements...> &in) {
        head(*this) = tuple_impl<N, _UElements...>::head(in);
        tail(*this).assign(tuple_impl<N, _UElements...>::tail(in));
    }

    static _Head &head(tuple_impl &t) {
        return t.value;
    }

    static const _Head &head(const tuple_impl &t) {
        return t.value;
    }

    static base_type &tail(tuple_impl &t) {
        return t;
    }

    static const base_type &tail(const tuple_impl &t) {
        return t;
    }

    _Head value;
};

template <typename... _Elements>
struct tuple : public tuple_impl<0, _Elements...> {

    typedef tuple_impl<0, _Elements...> base_type;

    constexpr tuple(const _Elements &... elements)
        : base_type(elements...) {}

    tuple &operator=(const tuple &in) {
        this->assign(in);
        return *this;
    }

    template <typename... _UElements>
    tuple &operator=(const tuple<_UElements...> &in) {
        this->assign(in);
        return *this;
    }
};

template <std::size_t N, typename... _Elements>
constexpr tuple<_Elements &...> tie(_Elements &... args) {
    return tuple<_Elements &...>(args...);
}

template <std::size_t N, typename... _Elements>
struct tuple_element;

template <std::size_t N, typename _Head, typename... _Tail>
struct tuple_element<N, tuple<_Head, _Tail...>> {
    using type = typename tuple_element<N - 1, tuple<_Tail...>>::type;
};

template <typename _Head, typename... _Tail>
struct tuple_element<0, tuple<_Head, _Tail...>> {
    using type = _Head;
};

template <std::size_t N, typename... _Elements>
using tuple_element_t = typename tuple_element<N, _Elements...>::type;

template <std::size_t N, typename _Head, typename... _Tail>
constexpr _Head &get_helper(tuple_impl<N, _Head, _Tail...> &t) {
    return tuple_impl<N, _Head, _Tail...>::head(t);
}

template <std::size_t N, typename _Head, typename... _Tail>
constexpr const _Head &get_helper(const tuple_impl<N, _Head, _Tail...> &t) {
    return tuple_impl<N, _Head, _Tail...>::head(t);
}

template <std::size_t N, typename... Args>
constexpr tuple_element_t<N, tuple<Args...>> &
get(tuple<Args...> &t) {
    return get_helper<N>(t);
}

template <std::size_t N, typename... Args>
constexpr const tuple_element_t<N, tuple<Args...>> &
get(const tuple<Args...> &t) {
    return get_helper<N>(t);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值