c++17中的apply和make_from_tuple

一、tuple的应用

前面对std::tuple进行了基础的学习,但是如何更好的应用std::tuple,在STL库里其实有更多的使用方式。在函数参数应用std::tuple的场景中,如何对std::tuple的数据进行操作,也即函数参数与其进行转换的方式。这里就得提到std::apply和std::make_from_tuple两个函数。前者提供了将std::tuple转化为参数并调用相关的函数。而后者功能是类似,只是处理对象时对构造函数的处理,非常有用。下面就分析一下这两个函数。

二、std::apply

先看一下定义:

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t);

看第一个模板形参,class F,一般来说,这个代表着函数。这里F可以表示函数,Lambda与表达式,仿函数及函数指针等。
在官网的文档中,给出一个可能的实现,也就是要实现一个自己的sequence,这就有点麻烦了。换句话说,如果没有这个std::apply,自己实现一个类似的功能,有点小复杂。在上面说了,这个函数的作用是把tuple自动拆封,对应成相关的参数,看一下例程:

#include <iostream>
#include <tuple>


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 <typename... T>
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:"<<r << std::endl;
    
    //类成员函数
    std::tuple t1(Example(),1,2,3,4,5,6);
    r = std::apply(&Example::Add,std::move(t1));
    std::cout << "result is:" << r << std::endl;

    //变参和Lambda
    //std::apply(TestVT,std::move(t));//错误
    std::apply([](auto &&... args) {return TestVT(args...); }, std::move(t));//利用新标准中Lambda表达式的auto实现调用

    return 0;
}

结果是:

result is:21
result is:21
6
123456

之所以给这么一个挺长参数的函数,目的就是为了更形象的说明这个函数。再看一下cppreference的例子:

#include <iostream>
#include <tuple>
#include <utility>
 
int add(int first, int second) { return first + second; }
 
template<typename T>
T add_generic(T first, T second) { return first + second; }
 
auto add_lambda = [](auto first, auto second) { return first + second; };
 
template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> 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 。对应着例子就应该明白了。

三、std::make_from_tuple

这个用来处理对象的,也就是说有构造函数,非平凡的。看一下例子:

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<Foo>(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 <typename T, typename... Args>
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<Foo>(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中其实很多,这也符合一个普通应用库的要求。不过,不是说这样的接口越多越好,还是尽量简化成较少的接口为妙。毕竟,学习的东西越多,反而会让开发者产生厌烦。过犹不及就是这个道理。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值