C++17前实现std::apply

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.comicon-default.png?t=N3I4https://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.comicon-default.png?t=N3I4https://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.comicon-default.png?t=N3I4https://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.comicon-default.png?t=N3I4https://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.comicon-default.png?t=N3I4https://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(...))可以获取返回值的类型, 见

C++ Trailing Return Types ‐ Daniel Siegericon-default.png?t=N3I4https://www.danielsieger.com/blog/2022/01/28/cpp-trailing-return-types.html

#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也不能使用), 当然你可以自己再通过形参包自己包装一个能够判断模板的, 我这里就不展开讲了

测试:

Compiler Explorertemplate<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());}template<size_t S, typename T = int>auto f1(T t1, T t2, T t3) -> T* { static T arr[S]{t1, t2, t3}; return arr;}std::string f2(char c, size_t num) { return std::string(num, c);}std::string f3(std::string& s1, std::string& s2) { return s1 + s2;}struct Functor1 { int operator() (int i, int j) { return i + j; }};struct Functor2 { int operator() (Functor1 f11, Functor1 f12, int a, int b, int c, int d) { return f11(a, b) + f12(c, d); };};int main() { int* p = my_apply(f1<3>, std::make_tuple(123, 456, 789)); std::cout << p[0] << ", " << p[1] << ", " << p[2] << '\n'; std::cout << my_apply(f2, std::make_tuple('A', 10)) << '\n'; auto t = std::make_tuple(std::string{"hello "}, std::string{"world"}); std::cout << my_apply(f3, t) << '\n'; int s; auto&& lambda = [&s](int i, int j, int k) -> int& { static int v; s = i + j + k; v = i * j * k; return v; }; int& v = my_apply(lambda, std::make_tuple(4, 5, 6)); std::cout << "s=" << s << ", v=" << v << '\n'; std::cout << "result: " << my_apply(Functor2{}, std::make_tuple(Functor1{}, Functor1{}, 1, 9, -4, 3)) << std::endl; return 0;}https://godbolt.org/z/zn1Ksa5r5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值