C++ std::tuple 学习笔记


概述

C++的std::tuple是在C++11中引入的,作为对C++标准库的增强。在此之前,C++标准库中的std::pair已经无法满足日益复杂的数据处理需求。因此,为了增强C++的数据处理能力,std::tuple被引入到了C++标准库中。在C++17中,又引入了变长模板和if constexpr等技术,使得我们可以更方便地使用和处理std::tuple。

用法总结

基本用法

使用std::tuple非常简单。首先,你需要包含头文件。然后,你可以创建一个tuple,将不同类型的值放入其中。例如:

#include <tuple>

int main() {
    std::tuple<int, double, char> my_tuple(10, 2.5, 'a');
    // 使用 std::get 来访问元素
    int first = std::get<0>(my_tuple);
    double second = std::get<1>(my_tuple);
    char third = std::get<2>(my_tuple);
    return 0;
}

在上述代码中,我们创建了一个包含三个不同类型元素的tuple。我们使用std::get来访问这些元素。注意,std::get的参数是一个从0开始的下标索引,所以std::get<0>表示访问第一个元素,std::get<1>表示访问第二个元素,以此类推。

std::tie

std::tie 用于解包一个元组并将元素分配给不同的变量。它的主要作用是生成一个 tuple 并解包其中的元素,以便我们可以使用这些元素。

int main() {
    auto t = std::make_tuple(1, "2", 0.5);
    int a;
    std::string b;
    double c;
    std::tie(a, b, c) = t;
    return 0;
}

但是随着 C++ 支持了 auto 的结构化绑定后,实现相同的解包操作更加简洁了。

int main() {
    auto t = std::make_tuple(1, "2", 0.5);
    auto [a, b, c] = t;
    return 0;
}

遍历

使用 std::tuple 作为 template 的时候,会经常需要一个功能就是遍历。但是 tuple 并没有直接遍历的方法,需要使用模板递归的方式。

template <size_t I = 0, typename FuncT, typename... Tp>
// sizeof...(Tp) 可以获取 tuple 参数的个数
// I == sizeof...(Tp) 就表明已经遍历到最后一个元素了,终止条件
inline typename std::enable_if_t<I == sizeof...(Tp)>
    for_each(std::tuple<Tp...>&, FuncT) {}

template <size_t I = 0, typename FuncT, typename... Tp>
// I < sizeof...(Tp) 说明元素没遍历完
inline typename std::enable_if_t <I < sizeof...(Tp)>
    for_each(std::tuple<Tp...>& t, FuncT f) {
  // f 函数操作每一个 tuple 的元素
  f(std::get<I>(t));
  // 每次I+1,递归调用下一个
  for_each<I + 1, FuncT, Tp...>(t, f);
}

可以使用 for_each 来对模板进行遍历打印。

int main() {
    auto t = std::make_tuple(1, "2", 0.5);
    for_each(t, [](auto i) {
        std::cout << i << std::endl;
    });
    return 0;
}

for_each 还可以使用 if constexpr 来简化终止条件,效果与上面的是一样的

template <size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if_t <I < sizeof...(Tp)>
    for_each(std::tuple<Tp...>& t, FuncT f) {
  f(std::get<I>(t));
  // 这里使用 if constexpr
  if constexpr (I + 1 < sizeof...(Tp))
    for_each<I + 1, FuncT, Tp...>(t, f);
}

变长模板

变长模板参数也可以转换为 std::tuple 来进行遍历,std::forward_as_tuple 可以将变长模板参数转换为 std::tuple 来处理,还要使用上面定义的 for_each。

template <typename... Args>
void print(const Args&... args) {
    auto t = std::forward_as_tuple(args...);
    for_each(t, [](auto x) {
        std::cout << x << std::endl;
    });
}

int main() {
    print(1, 2, 3, "4", '5', 6.0);
    return 0;
}

展开

std::apply是C++17中引入的一个函数模板,位于头文件中,可以展开 std::tuple 中的元素。

基本使用方法:

int main() {
    std::tuple<int, int> t(2, 3);
    // 这里的 add 也可以使用 lambda function
    auto result = std::apply([](auto a, auto b) {
    	return a + b;
    }, t);
    std::cout << result << std::endl;  // 输出 5
    return 0;
}

结合 fold expression 的语法,可以做到遍历的效果。

int main() {
    auto t = std::make_tuple(1, "2", 0.5);
    std::apply([](auto... args) {
        ((std::cout << args << std::endl), ...);
    }, t);
    return 0;
}

总结

std::tuple 是一个通用的数据结构,可以支持多个异构类型的数据,并且在变长模板范型中也有较多的用武之地。

参考资料

  1. std::tuple - cppreference

如果觉得文章对您有帮助,用微信请作者喝杯咖啡吧!这样他会更有动力,分享更多更好的知识!

wechat赞赏