前面对std::tuple进行了基础的学习,但是如何更好的应用std::tuple,在STL库里其实有更多的使用方式。在函数参数应用std::tuple的场景中,如何对std::tuple的数据进行操作,也即函数参数与其进行转换的方式。这里就得提到std::apply和std::make_from_tuple两个函数。前者提供了将std::tuple转化为参数并调用相关的函数。而后者功能是类似,只是处理对象时对构造函数的处理,非常有用。下面就分析一下这两个函数。
先看一下定义:
template
constexpr decltype(auto) apply(F&& f, Tuple&& t);
看第一个模板形参,class F,一般来说,这个代表着函数。这里F可以表示函数,Lambda与表达式,仿函数及函数指针等。
在官网的文档中,给出一个可能的实现,也就是要实现一个自己的sequence,这就有点麻烦了。换句话说,如果没有这个std::apply,自己实现一个类似的功能,有点小复杂。在上面说了,这个函数的作用是把tuple自动拆封,对应成相关的参数,看一下例程:
#include
#include
int Add(int a, int b, int c,int d,int e,int f)
{
return a + b + c + d + e + f;
}
class Example
{
public:
int Add(int a, int b, int c, int d, int e, int f)
{
return a + b + c + d + e + f;
}
};
template
void TestVT(T... args)
{
std::cout << sizeof... (args) << std::endl;
(std::cout << ... << args) << std::endl;
}
int main() {
//普通函数
std::tuple t(1,2,3,4,5,6);
int r = std::apply(Add, std::move(t));
std::cout << "result is:"<
结果是:
result is:21
result is:21
6
123456
之所以给这么一个挺长参数的函数,目的就是为了更形象的说明这个函数。再看一下cppreference的例子:
#include
#include
#include
int add(int first, int second) { return first + second; }
template
T add_generic(T first, T second) { return first + second; }
auto add_lambda = [](auto first, auto second) { return first + second; };
template
std::ostream& operator<<(std::ostream& os, std::tuple const& theTuple)
{
std::apply
(
[&os](Ts const&... tupleArgs)
{
os << '[';
std::size_t n{0};
((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
os << ']';
}, theTuple
);
return os;
}
int main()
{
// OK
std::cout << std::apply(add, std::pair(1, 2)) << '\n';
// 错误:无法推导函数类型
// std::cout << std::apply(add_generic, std::make_pair(2.0f, 3.0f)) << '\n';
// OK
std::cout << std::apply(add_lambda, std::pair(2.0f, 3.0f)) << '\n';
// 进阶示例
std::tuple myTuple(25, "Hello", 9.31f, 'c');
std::cout << myTuple << '\n';
}
结果是:
3
5
[25, Hello, 9.31, c]
在这个网页上有句说明:元组不必是 std::tuple ,可以为任何支持 std::get 和 std::tuple_size 的类型所替代;特别是可以用 std::array 和 std::pair 。对应着例子就应该明白了。
这个用来处理对象的,也就是说有构造函数,非平凡的。看一下例子:
class Foo
{
public:
Foo(int first, float second, int third)
{
std::cout << first << ", " << second << ", " << third << std::endl;
}
int operator ()()
{
std::cout << "is call" << std::endl;
return 1;
}
};
void TestMake()
{
auto tuple = std::make_tuple(42, 3.14f, 0);
auto a = std::make_from_tuple(std::move(tuple));
std::cout << "return is:" << a() << std::endl;
}
int main()
{
TestMake();
return 0;
}
多么简单,而如果没有这个函数,就得自己封装并解析std::tuple中的参数,转化成对象。这就是利用上面的变参模板函数和Lambda表达式了。
class Foo
{
public:
Foo(int a, double b, const std::string& c,int d) : a_(a), b_(b), c_(c),d_(d) {}
int operator ()()
{
std::cout << a_ << " " << b_ << " " << c_ << " " << d_ << std::endl;
return 1;
}
private:
int a_,d_=0;
double b_ = 0.0;
std::string c_ = "";
};
// 自行封装构造过程
template
T Instance(Args &&...args)
{
return T(args...);
}
void TestOwner()
{
std::tuple t(3, 1.9, "test",8);
Foo&& f = std::apply([](auto &&...args)->Foo {return Instance(args...); }, std::move(t));
f();
}
int main()
{
TestOwner();
return 0;
}
反复提到过一句话,简单才是王道。为什么js和python流行,原因何在?所谓功能强大,其实都是在简单的基础上喊出来的。c++功能更强大,怎么没有他们流行?这就是原因,不简单。
std::apply和std::make_from_tuple可以说是一对相互配合操作的工具接口,通过这两个就可以把一些较为复杂的对std::tuple的操作变得容易简单。看STL库中,还有一个forward_as_tuple函数,基本都差不多,有兴趣可以认真看看。
其实在实际工作也可以发现,一个好的接口往往比实现本身更容易为人所称道。本文的这两个函数,其实就可以理解为std::tuple基础设施,让其在实际应用中更方便快捷安全的使用的一个接口。这个现象在STL中其实很多,这也符合一个普通应用库的要求。不过,不是说这样的接口越多越好,还是尽量简化成较少的接口为妙。毕竟,学习的东西越多,反而会让开发者产生厌烦。过犹不及就是这个道理。