C++17中加入的std::apply可以将让我们直接将元组拆包将元组中的成员直接转发给目标函数, 比如
#include <iostream>
#include <utility>
#include <tuple>
void func(int i, char c, std::string str) {
std::cout << i << ", " << c << ", " << str << std::endl;
}
int main() {
auto args = std::make_tuple(1, 'A', std::string{"hello"});
//args类型为 std::tuple<int, char, std::string>
std::apply(func, args); // 使用C++17标准的apply
return 0;
}
关于std::apply的详细介绍和用法可以参考
std::apply - cppreference.comhttps://zh.cppreference.com/w/cpp/utility/apply如果我想在C++17之前使用的话, 就需要自己实现一个; 我们可以知道元组可以使用std::get访问其成员, 比如
#include <iostream>
#include <utility>
#include <tuple>
int main() {
auto t = std::make_tuple(1, "hello", 3.14f);
/*1) 通过下标访问*/
std::get<0>(t); //获取元组第一个成员1
std::get<2>(t); //获取元组第三个成员3.14f
/*2) 通过类型访问*/
std::get<float>(t); //获取元组类型为float的成员
//不过需要注意的是: 如果元组有多个同类型的成员, 不能通过类型访问, 以下代码错误
//std::tuple<int, int, const char*> t1{123, 456, "world"};
//std::get<int>(t1); //由于元组第一个成员和第二个成员都是int
return 0;
}
关于std::get的详细介绍即用法, 可以参考
std::get(std::tuple) - cppreference.comhttps://zh.cppreference.com/w/cpp/utility/tuple/get所以我可以使用std::get通过下标访问成员, 将元组成员传参给函数
#include <iostream>
#include <utility>
#include <tuple>
void func(int i1, int i2, std::string str) {
std::cout << i1 << ", " << i2<< ", " << str << '\n';
}
int main() {
auto t = std::make_tuple(1, 2, std::string{"hhh"});
func(std::get<0>(t), std::get<1>(t), std::get<2>(t));
return 0;
}
当然我们要进一步抽象这个过程(毕竟我们要实现的apply是一个不管元组有多少成员都可以使用的)
#include <iostream>
#include <utility>
#include <tuple>
template<size_t... Indexes, typename Func, typename Tuple>
void apply(Func&& f, Tuple&& t) {
// 此处Tuple&&为万能引用, 毕竟我们应该允许传入值类别为右值的元组
f(std::get<Indexes>(std::forward<Tuple>(t))...);
/*
get时也应该对t进行转发, 毕竟get对值类别为左值的元组和对右值的处理不同,
有兴趣自己可以看看其实现, 不在此赘述
*/
}
void func(int i1, int i2, std::string str) {
std::cout << i1 << ", " << i2 << ", " << str << '\n';
}
int main() {
auto t = std::make_tuple(1, 2, std::string{"hhh"});
apply<0, 1, 2>(func, t);
return 0;
}
我们可以借助非类型形参包, 来抽象这一过程, 关于非类型形参包的介绍与使用可参考
形参包 (C++11 起) - cppreference.comhttps://zh.cppreference.com/w/cpp/language/parameter_pack但是可以看到下标仍需要我们自己传入, 那么有没有办法根据元组的大小(假设为n)生成0, 1, ..., n-1这样的序列呢?
有! 那就是C++14中的std::make_index_sequence及std::make_integer_sequence
std::integer_sequence - cppreference.comhttps://zh.cppreference.com/w/cpp/utility/integer_sequencestd::make_index_sequence<N>() 可以生成std::integer_sequence<size_t, 0, 1, 2, ..., N-1>
我们只要萃取到index_sequence(integer_sequence<size_t>的别名)中的数字就行了
#include <iostream>
#include <tuple>
#include <utility>
template<size_t... Indexes, typename Func, typename Tuple>
void apply_impl(Func&& f, Tuple&& t, std::index_sequence<Indexes...>) {
f(std::get<Indexes>(std::forward<Tuple>(t))...);
}
template<typename Func, typename Tuple>
void apply(Func&& f, Tuple&& t) {
apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type>::value>());
//可以使用std::tuple_size获取元组的大小, 获取大小之前先要remove_reference, 因为Tuple可能被推断为std::tuple<...>&
}
void func(int i1, int i2, std::string str) {
std::cout << i1 << ", " << i2 << ", " << str << '\n';
}
int main() {
auto t = std::make_tuple(1, 2, std::string{"hhh"});
apply(func, t);
return 0;
}
就这样自动拆包传参的问题就解决了。现在我们还想获取func的返回值, 可以使用C++14中的decltype(auto)它能自动推断返回值类型(其实auto(auto&, auto&&)等也可以推断, 但由于它们本身推导规则的原因(比如出现类型退化), 不能准确推导函数的返回值类型;
关于decltype(auto)及auto, auto&, auto&&的推导规则:
占位类型说明符 (C++11 起) - cppreference.comhttps://zh.cppreference.com/w/cpp/language/auto
#include <iostream>
#include <tuple>
#include <utility>
template<size_t... Indexes, typename Func, typename Tuple>
decltype(auto) apply_impl(Func&& f, Tuple&& t, std::index_sequence<Indexes...>) {
return f(std::get<Indexes>(std::forward<Tuple>(t))...);
}
template<typename Func, typename Tuple>
decltype(auto) apply(Func&& f, Tuple&& t) {
return apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type>::value>());
}
int func(int i1, int i2, std::string str) {
std::cout << i1 << ", " << i2 << ", " << str << '\n';
return 123;
}
int main() {
auto t = std::make_tuple(1, 2, std::string{"hhh"});
auto res = apply(func, t);
std::cout << "`func` return " << res << '\n';
return 0;
}
现在我们的apply可以再C++14的环境中使用了, 但不能在C++11的环境中使用, 因为integer_sequence以及decltype(auto)都是C++14后才有的
我们如何在C++11中实现呢?
其实make_index_sequence我们可以自己实现, 至于目标函数返回值类型我们也有办法(通过后置返回值以及decltype)获取。
实现make_index_sequence:
template<size_t N, size_t... Rests>
struct make_index_seq : make_index_seq<N-1, N, Rests...> {};
template<size_t... Rests>
struct make_index_seq<0, Rests...> {
using result = make_index_seq<0, Rests...>;
};
对于上述代码的解释: 假设传入一个数字n:
make_index_seq<n>继承自make_index_seq<n-1, n>,
make_index_seq<n-1, n>继承自make_index_seq<n-2, n-1, n>,
make_index_seq<n-2, n-1, n>继承自make_index_seq<n-3, n-2, n-1, n>,
......
make_index_seq<1, 2, ..., n>继承自make_index_seq<0, 1, 2, ..., n>
由类成员名字查找的规则(自己的类定义中查找不到成员, 会去基类中查找), make_index_seq<n>::result会查找到make_index_seq<0, 1, 2, 3, ..., n>的result
而make_index_seq<0, 1, 2, .., n>的成员result是自身的别名, 也就是说make_index_seq<n>::result就会得到make_index_seq<0, 1, 2, ..., n>
假设一个元祖的大小为m, 我们传入m-1给make_index_seq是, 就会得到make_index_seq<0, 1, 2, 3, ..., m-1>, 我们对make_index_seq进行萃取就能得到0, 1, 2, ..., m-1
#include <iostream>
#include <tuple>
#include <utility>
template<size_t N, size_t... Rests>
struct make_index_seq : make_index_seq<N-1, N, Rests...> {};
template<size_t... Rests>
struct make_index_seq<0, Rests...> {
using result = make_index_seq<0, Rests...>;
};
template<size_t... Indexes, typename Func, typename Tuple>
decltype(auto) apply_impl(Func&& f, Tuple&& t, make_index_seq<Indexes...>) {
return f(std::get<Indexes>(std::forward<Tuple>(t))...);
}
template<typename Func, typename Tuple>
decltype(auto) apply(Func&& f, Tuple&& t) {
return apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), typename make_index_seq<std::tuple_size<typename std::remove_reference<Tuple>::type>::value - 1>::result());
}
int func(int i1, int i2, std::string str) {
std::cout << i1 << ", " << i2 << ", " << str << '\n';
return 123;
}
int main() {
auto t = std::make_tuple(1, 2, std::string{"hhh"});
auto res = apply(func, t);
std::cout << "`func` return " << res << '\n';
return 0;
}
最后就差替换掉decltype(auto), 就能在C++11中使用了。我们想要的是func的返回值类型是什么, apply的返回值类型就是什么,而后置返回值+decltype(func(...))可以获取返回值的类型, 见
#include <iostream>
#include <tuple>
#include <utility>
template<size_t N, size_t... Rests>
struct make_index_seq : make_index_seq<N - 1, N, Rests...> {};
template<size_t... Rests>
struct make_index_seq<0, Rests...> {
using result = make_index_seq<0, Rests...>;
};
template<size_t... Indexes, typename Func, typename Tuple>
auto apply_impl(Func&& f, Tuple&& t, make_index_seq<Indexes...>)
-> decltype(f(std::get<Indexes>(std::forward<Tuple>(t))...))
{
return f(std::get<Indexes>(std::forward<Tuple>(t))...);
}
template<typename Func, typename Tuple>
auto apply(Func&& f, Tuple&& t)
-> decltype(apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), typename make_index_seq<std::tuple_size<typename std::remove_reference<Tuple>::type>::value - 1>::result()))
{
return apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), typename make_index_seq<std::tuple_size<typename std::remove_reference<Tuple>::type>::value - 1>::result());
}
int func(int i1, int i2, std::string str) {
std::cout << i1 << ", " << i2 << ", " << str << '\n';
return 123;
}
int main() {
auto t = std::make_tuple(1, 2, std::string{"hhh"});
auto res = apply(func, t);
std::cout << "`func` return " << res << '\n';
return 0;
}
由于decltype中是不求值语境(只获取编译时的信息, 如类型), 所以decltype(...)中的函数不会被调用。最后需要注意的是由于tuple属于namespace std, 根据ADL(实参依赖查找)会找到std::apply, 所以我们的这个apply在C++17以后会与std::apply发生冲突, 所以最好将自己实现的apply改成别的函数名, 或者加入到别的名称空间,反正最好别放在全局空间。
#include <tuple>
#include <utility>
template<size_t N, size_t... Rests>
struct make_index_seq : make_index_seq<N - 1, N, Rests...> {};
template<size_t... Rests>
struct make_index_seq<0, Rests...> {
using result = make_index_seq<0, Rests...>;
};
template<size_t... Indexes, typename Func, typename Tuple>
auto my_apply_impl(Func&& f, Tuple&& t, make_index_seq<Indexes...>)
-> decltype(f(std::get<Indexes>(std::forward<Tuple>(t))...))
{
return f(std::get<Indexes>(std::forward<Tuple>(t))...);
}
template<typename Func, typename Tuple>
auto my_apply(Func&& f, Tuple&& t)
-> decltype(my_apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), typename make_index_seq<std::tuple_size<typename std::remove_reference<Tuple>::type>::value - 1>::result()))
{
return my_apply_impl(std::forward<Func>(f), std::forward<Tuple>(t), typename make_index_seq<std::tuple_size<typename std::remove_reference<Tuple>::type>::value - 1>::result());
}
还有就是我们自己实现的这个apply不能直接传入函数模板名(即便是std::apply也不能使用), 当然你可以自己再通过形参包自己包装一个能够判断模板的, 我这里就不展开讲了
测试: