C++17之std::apply与std::make_from_tuple
C++17之std::apply与std::make_from_tuple
C++17中有两个有意思的语法,是关于std::tuple
(或std::pair
、std::array
等可以通过std::get
来操作成员的元组容器)与函数参数转化的问题的。今天这篇文章我们来研究一下。
std::apply
std::apply最主要的作用就是把tuple转化为函数参数,然后去调用函数。如果没有STL,我们需要自行去实现,首先需要实现一个sequence
,里面是从0到size,分别对应tuple
中的每一个参数,然后再通过std::get
和折叠展开语法来把tuple中的数据展开到参数列表中,比如这样:
template <int... Seq>
struct sequence {};template <int N, int... Seq>
struct make_seq : make_seq<N - 1, N - 1, Seq...> {};template <int... Seq>
struct make_seq<0, Seq...> {using type = sequence<Seq...>;
};template <typename Func, typename Tuple, int... Seq>
decltype(auto) Apply_detail(const Func &func, const Tuple &tu, const sequence<Seq...> &) {return func(std::get<Seq>(tu)...); // 参数展开
}template <typename Func, typename Tuple>
decltype(auto) Apply(const Func &func, const Tuple &tu) {return Apply_detail(func, tu, typename make_seq<std::tuple_size<Tuple>::value>::type());
}void Show(int a, double b, const std::string &c) {std::cout << a << " " << b << " " << c << std::endl;
}int main(int argc, const char *argv[]) {std::tuple tu(1, 2.5, "abc");Apply(Show, tu);return 0;
}
这个seq的生成可能有点难懂,简单来说,假如我们tuple
有4个参数,那么我们要把make_seq<4>
变成make_seq<3, 3>
,再变成make_seq<2, 2, 3>
,再变成make_seq<1, 1, 2, 3>
,再变成make_seq<0, 0, 1, 2, 3>
,当第一个参数是0的时候,后面不再变化,这时候正好可以得到从0到3的序列,然后用这个参数序列构造出sequence<0, 1, 2, 3>
。然后,再利用这个序列,传给std::get
来处理tuple
。
折贴语句func(std::get<Seq>(tu)...)
就变成了func(std::get<0>(tu), std::get<1>(tu), std::get<2>(tu), std::get<3>(tu))
,从而达成我们的目的。
而有了std::apply
,它可以实现同样的功能,请看例子:
#include <iostream>int sum(int a, int b, int c) {return a + b + c;
}int main(int argc, const char *argv[]) {std::tuple tu(1, 2, 3);int res = std::apply(sum, std::move(tu));std::cout << res << std::endl; // 输出6return 0;
}
除了普通函数,apply的第一个参数还可以是函数指针、lambda、仿函数对象,不再赘述。
那么,对于非静态成员函数怎么办?很简单,把需要调用的对象作为tuple
中的第一个成员即可,例如:
#include <iostream>
struct TT {int sum(int a, int b, int c) {return a + b + c;}
};int main(int argc, const char *argv[]) {std::tuple tu(TT(), 1, 2, 3); // 第一个成员就是调用成员int res = std::apply(&TT::sum, std::move(tu)); // 这里传成员函数指针// 效果相当于std::get<0>(tu).sum(1, 2, 3)std::cout << res << std::endl;return 0;
}
变参模板应用std::apply
那么,对于变参模板要怎么办?由于std::apply
的第一个参数是一个可调用对象,因此,对于模板来说,必须要进行实例化。而对于变参模板来说,它本身存在意义就在于可以根据参数来自动推导出模板的类型,如果要手动实例化的话,就会失去意义,请看例程:
#include <iostream>
template <typename... T>
void test(T... arg) {(std::cout << ... << arg) << std::endl;
}int main(int argc, const char *argv[]) {std::tuple tu(1, 2.5, "abc");std::apply(test, std::move(tu)); // errstd::apply(test<int, double, const char *>, std::move(tu)); // 没意义return 0;
}
怎么办呢?其实,可以简单用lambda封装一下就好了,把变参目标的调用放在lambda当中,然后把类型推导的任务交给lambda本身即可,也就是auto
大法好啦。请看例程:
#include <iostream>template <typename... T>
void test(T... arg) {(std::cout << ... << arg) << std::endl;
}int main(int argc, const char *argv[]) {std::tuple tu(1, 2.5, "abc");std::apply([](auto &&... args) {return test(args...);}, std::move(tu)); // 正常调用return 0;
}
std::make_from_tuple
std::apply
可以解决函数调用时,tuple
转参数列表,但是如果希望调用的是构造函数怎么办?构造函数毕竟没办法直接获取函数指针。
其中一种办法就是自行封装一层模板,例如:
class Test {public:Test(int a, double b, const std::string &c): a_(a), b_(b), c_(c) {}void show() const {std::cout << a_ << " " << b_ << " " << c_ << std::endl;}
private:int a_;double b_;std::string c_;
};// 自行封装构造过程
template <typename T, typename... Args>
T Create(Args &&...args) {return T(args...);
}int main(int argc, const char *argv[]) {std::tuple tu(1, 2.5, "abc");Test &&t = std::apply([](auto &&...args)->Test {return Create<Test>(args...);}, std::move(tu));t.show(); // 打印:1 2.5 abcreturn 0;
}
而STL提供的std::make_from_tuple
就是同样的作用,我们可以直接用它来代替自行实现的Create
函数:
#include <iostream>// Test实现同上,省略int main(int argc, const char *argv[]) {std::tuple tu(1, 2.5, "abc");Test &&t = std::make_from_tuple<Test>(std::move(tu));t.show();return 0;
}
C++17之std::apply与std::make_from_tuple相关推荐
- 【C++ 泛型编程 高级篇】 C++ 17 解析std::apply 的多种应用场景
目录标题 1. 引言 1.1. C++17标准的引入 1.2. std::apply的基本概念 2. std::apply的基本用法 2.1. std::apply的函数签名 2.2. std::ap ...
- C++中std::function和std::bind
1.可调用对象 可调用对象有一下几种定义: 是一个函数指针,参考 C++ 函数指针和函数类型: 是一个具有operator()成员函数的类的对象: 可被转换成函数指针的类对象: 一个类成员函数指针: ...
- std::future和std::promise和std::packaged_task
std::future 其实future有两个兄弟,一个是std::future, 一个是它大哥std::shared_future.他们的区别就是std::future只支持移动语义,它所引用的共享 ...
- [C/C++]关于C++11中的std::move和std::forward
http://blog.sina.com.cn/s/blog_53b7ddf00101p5t0.html std::move是一个用于提示优化的函数,过去的c++98中,由于无法将作为右值的临时变量从 ...
- c++11 std::bind与std::function
function模板类和bind模板函数,使用它们可以实现类似函数指针的功能,但却比函数指针更加灵活,特别是函数指向类的非静态成员函数时. std::function可以绑定到全局函数/类静态成 ...
- C++的std::is_same与std::decay
一.背景 有一个模板函数,函数在处理int型和double型时需要进行特殊的处理,那么怎么在编译期知道传入的参数的数据类型是int型还是double型呢? #include <iostream& ...
- C++——std::async和std::thread
作者:小 琛 欢迎转载,请标明出处 参考文章: 傻月菇凉博主-博客 OceanStar的学习笔记博主-博客 NGC_2070博主-博客 文章目录 std::thread thread的提出 使用方法. ...
- 【C++ 泛型编程 入门篇】C++元模版中std::remove_reference_t和std::remove_cv_t的运用
目录标题 1. std::remove_reference_t和std::remove_cv_t简介 1.1 函数原型及基本解释 1.2 在C++11, C++14, C++17, C++20中的表现 ...
- C++ std::any、std::variant和std::optional的原位构造(In-Place Construction)
本文翻译自 Bartlomiej Filipek 的博客文章 In-Place Construction for std::any, std::variant and std::optional,翻译 ...
- std::variant 与 std::visit
std::variant 简介 std::variant 是c++17 引入的一个类型,其作用类似于C语言中的Union,但是比Union 的功能强大的多. C语言中一个联合体Union 可以储存多种 ...
最新文章
- mysql数据库隐式表_详解MySQL数据库常见的索引问题:无索引,隐式转换,附实例说明...
- svn提交怎么全选_做外贸怎么精准开发国外客户?终于有答案了
- C++ vector多维数组初始化及清零
- css 浮动问题 display显示 和 光标设置cursor
- jzoj4208-线段树什么的最讨厌了【dfs】
- java调用scilab_Java调用Scilab-编译运行Javasci v2
- iOS CoreData简单入门 - Swift版
- 用 Java 开发自己的 Kubernetes 控制器,想试试吗?
- 多线程下单例模式:懒加载(延迟加载)和即时加载
- STM32标准库官网下载方法
- 【优化算法】爬虫搜索算法(RSA)【含Matlab源码 1838期】
- 转正述职报告 实习转正 工作汇报 述职模板免费下载_PPTX图片设计素材_包图网888pic.com...
- 在资源管理器中不小心关掉了什么,win10桌面不见了,变黑了
- java路径的上一级_java路径两种写法/和\\以及 ./和../以及/之间的区别?
- ⚓写写5G网速及页面提速中的延迟加载Lazyloading
- 【转】Windows Linux MacOS操作系统的区别
- day fit into much one too_PGone Talking too much歌词
- 磁盘IOPS概念及IOPS的计算与测试
- item_search_img-按图搜索1688商品(拍立淘)接口的接入参数说明
- 分省增值税和营业税数据(2009-2019年)