std::tuple应用实践
  ZXL8ALkrtBPG 2023年11月02日 47 0
cpp

std::tuple为C++11引入的特性,它可以容纳任意类型的元素,同时具有编译期计算的特性,在元编程中也有广泛的应用,在之前博文【3】中也利用tuple实现了默认模板参数的扩展,本文结合其他实践来介绍tuple的应用。

基础功能

tuple创建与元素获取

using boost::typeindex::type_id_with_cvr;

int main()
{
    int n = 1;
    auto t = std::make_tuple(10, "Test", 3.14, n);  
    std::cout << "type:" << type_id_with_cvr<decltype(t)>().pretty_name() << std::endl;
    std::cout << "The value of t is ("
              << std::get<0>(t) << ", "
              << std::get<1>(t) << ", "
              << std::get<2>(t) << ", "
              << std::get<3>(t) << ")\n";
}

输出

type:std::tuple<int, char const*, double, int>
The value of t is (10, Test, 3.14, 1)

通过std::make_tuple创建tuple对象,对应的类型std::tuple<int, char const*, double, int>;

也可以通过std::tuple<int, int, std::string> t {0, 1, "Test"}直接创建对象;

如果希望创建的是类型引用,则可以修改:

int n = 1;
auto t = std::make_tuple(10, "Test", 3.14, std::ref(n)); 
// 对n修改则tuple中的也会发生改变

输出:

std::tuple<int, char const*, double, int&>

通过std::get获取指定位置的元素;

遍历

实现类似std::for_each进行遍历tuple中的元素:

struct Functor {                         
    template<typename T>                 
    void operator()(T&& t)               
    {                                    
        std::cout << t << std::endl;     
    }                                    
};                                       
int main()
{
    auto t = std::make_tuple(513, "Test", 3.14);
    TupleForeach(Functor(), t);
}

输出:

513
Test
3.14

TupleForeach实现:

template<size_t N>
struct TupleForeachHelper {
    template<typename F, size_t I, size_t ...Is, typename TupleType>
    static void Execute(F& f, std::index_sequence<I, Is...> index, TupleType& tup)
    {   
        f(std::get<I>(tup));
        TupleForeachHelper<sizeof...(Is)>::Execute(f, std::index_sequence<Is...> {}, tup);
    }   
};

template<>
struct TupleForeachHelper<0> {
    template<typename F, size_t ...Is, typename TupleType>
    static void Execute(F& f, std::index_sequence<Is...> index, TupleType& tup) {}
};

template<typename F, typename ...Args>
void TupleForeach(F&& f, std::tuple<Args...>& tup)
{
    TupleForeachHelper<sizeof...(Args)>::Execute(f, std::make_index_sequence<sizeof...(Args)> {}, tup);
}

借助std::index_sequence的非类型模板参数,将tup中的元素递归依次获取进行遍历。

打印

通过TupleForeach也能实现打印的功能,但如果仅仅只是打印则可以通过可变参表达式进行简化:

namespace Detail {

template<size_t ...Is, typename TupleType>
void TupelPrintImpl(std::index_sequence<Is...>, TupleType& tup)
{
    int a[] = {(std::cout << std::get<Is>(tup) << ", ", 0)...}; // 可变参表达式
    std::cout << std::endl;
}
}

template<typename ...Args>
void TuplePrint(std::tuple<Args...>& tup)
{
    Detail::TupelPrintImpl(std::make_index_sequence<sizeof...(Args)>{}, tup);
}

输出:

513, Test, 3.14,

拼接

int main()
{
    std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    int n = 7;
    auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n));
    n = 10;
    TuplePrint(t2);
}

std::tuple_cat将多个tuple拼接成了一个新的tuple。

注:std::tie(n)返回的类型为std::tuple<int &>

获取元素类型

std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    std::cout << "type:" << type_id_with_cvr<std::tuple_element<2, decltype(t1)>::type>().pretty_name() << std::endl;

通过std::tuple_element便可以在编译期获取tup中指定位置的元素类型,tuple_element实现参考【2】:

template< std::size_t I, class T >
struct tuple_element;
 
// recursive case
template<std::size_t I, class Head, class... Tail >
struct tuple_element<I, std::tuple<Head, Tail...>>
    : std::tuple_element<I-1, std::tuple<Tail...>> { };
 
// base case
template< class Head, class... Tail >
struct tuple_element<0, std::tuple<Head, Tail...>> {
   typedef Head type;
};

应用实践

函数Pipeline

std::tuple应用实践_CPP

通过stuple实现函数pipeline串联:

template<typename ...Funs>
class FunChain {
public:
    FunChain() : m_funTuple(std::tuple<> {}) {}
    FunChain(std::tuple<Funs...> funTuple) : m_funTuple(funTuple) {}

    template<typename F>
    auto Then(F& f)
    {
        return FunChain<Funs..., F>(std::tuple_cat(m_funTuple, std::make_tuple(f)));
    }

    template<typename ...Args>
    auto operator()(Args&& ...args)
    {
        return Call(std::make_index_sequence<sizeof...(Funs)> {}, std::forward<Args>(args)...);
    }
private:
    template<std::size_t I, std::size_t ...Is, typename ...Args>
    auto Call(std::index_sequence<I, Is...>, Args&&... args)
    {
        return Call(std::index_sequence<Is...> {}, std::get<I>(m_funTuple)(std::forward<Args>(args)...));
    }

    template<std::size_t I, typename ...Args>
    auto Call(std::index_sequence<I>, Args&&... args)
    {
        return std::get<I>(m_funTuple)(std::forward<Args>(args)...);
    }

    std::tuple<Funs...> m_funTuple;
};
int Fun1(int val)
{
    return val + 2;
}

int Fun2(int val)
{
    return val + 3;
}

auto val = (fc.Then(Fun1).Then(Fun2))(0); // val = 5

基于FunChain添加Fork:

int Fun3(int val1, int val2)
{
    return val1 + val2;
}

auto val = (fc.Then(Fun1).Fork(Fun2, Fun2).Then(Fun3))(0);  // val = 10

std::tuple应用实践_CPP_02

通过fork实现将Fun1的结果分发给两个Fun2、Fun2,而将两个Fun2输出作为Fun3的输入,即Fun3有两个入参,因此val1 = 5,val2 = 5,最终val = 10; 修改之后完整的参考实现代码如下:

template<typename ...Args>
struct ForkRetType {
    using Type = std::tuple<typename FunctionTraits<Args>::RetType...>;
};

template<typename ...Funs>
class FunFork {
public:
    FunFork(Funs&...funs) : m_funTuple(std::make_tuple(funs...)) {}
    using TupRetType = typename ForkRetType<std::decay_t<Funs>...>::Type;

    template<typename ...Args>
    TupRetType operator()(Args&& ...args)
    {
        return Call(std::make_index_sequence<sizeof...(Funs)>(), std::forward<Args>(args)...);
    }
private:
    template<std::size_t ...Is, typename ...Args>
    auto Call(std::index_sequence<Is...>, Args&&... args)
    {
        return std::make_tuple(std::get<Is>(m_funTuple)(std::forward<Args>(args)...)...);
    }

    std::tuple<Funs...> m_funTuple;
};

template<typename ...Funs>
class FunChain {
public:
    FunChain() : m_funTuple(std::tuple<> {}) {}
    FunChain(std::tuple<Funs...> funTuple) : m_funTuple(funTuple) {}

    template<typename F>
    auto Then(F f)
    {
        return FunChain<Funs..., F>(std::tuple_cat(m_funTuple, std::make_tuple(f)));
    }

    template<typename ...CallerTypes>
    auto Fork(CallerTypes... callers)
    {
        return FunChain<Funs..., FunFork<CallerTypes...>>(std::tuple_cat(m_funTuple,
                                                          std::make_tuple(FunFork<CallerTypes...>(callers...))));
    }

    template<typename ...Args>
    auto operator()(Args&& ...args)
    {
        return Call(std::make_index_sequence<sizeof...(Funs)> {}, std::forward<Args>(args)...);
    }
private:
    template<typename CallerType, size_t ...Is, typename ...Args>
    auto TupleApply(CallerType&& caller, std::index_sequence<Is...>, std::tuple<Args...>& tup)
    {
        return caller(std::get<Is>(tup)...);
    }
    template<typename Caller, typename ...Args>
    auto CallHelper(Caller caller, std::tuple<Args...>& tup)
    {
        return TupleApply(caller, std::make_index_sequence<sizeof...(Args)> {}, tup);
    }
    template<typename Caller, typename ...Args>
    auto CallHelper(Caller caller, Args&&... args)
    {
        return caller(std::forward<Args>(args)...);
    }

    template<std::size_t I, std::size_t ...Is, typename ...Args>
    auto Call(std::index_sequence<I, Is...>, Args&&... args)
    {
        auto nextArgs = CallHelper(std::get<I>(m_funTuple), std::forward<Args>(args)...);
        return Call(std::index_sequence<Is...> {}, nextArgs);
    }

    template<std::size_t I, typename ...Args>
    auto Call(std::index_sequence<I>, Args&&... args)
    {
        return CallHelper(std::get<I>(m_funTuple), std::forward<Args>(args)...);
    }

    std::tuple<Funs...> m_funTuple;
};

将需要Fork的函数对象封装到ForkType可调用对象中,示意图如下:

std::tuple应用实践_CPP_03

函数仅对入参进行逻辑处理,本身并无修改程序状态,相同的输入总是能得到相同的结果;

参考资料

【1】函数式编程

【2】https://en.cppreference.com/w/cpp/utility/tuple

【3】Default Arguments总结

【4】深入应用C++11-代码优化与工程级应用

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  ZXL8ALkrtBPG   2023年11月02日   47   0   0 cpp
  PQYWJLBjS0G7   2023年11月02日   47   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   66   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   38   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   39   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   46   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   76   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   31   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   26   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   33   0   0 cpp
  ZXL8ALkrtBPG   2023年11月19日   18   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   54   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   68   0   0 Listcpp
  ZXL8ALkrtBPG   2023年11月02日   46   0   0 cpp
  ZXL8ALkrtBPG   2023年11月02日   66   0   0 cpp
  ZXL8ALkrtBPG   2023年11月19日   31   0   0 cpp
ZXL8ALkrtBPG