利用可变模板参数实现log功能

35 篇文章 6 订阅
26 篇文章 1 订阅

在以前的博文中,写过类似的课题。使用的是下面这种方法。

// 递归出口
template <typename T>
void logOld(const T& t)
{
    std::cout << t << '\n';
}

// 递归展开
template <typename T, typename ... Args>
void logOld(const T& t, const Args& ... args)
{
    std::cout << t << ' ';
    logOld(args...);
}

函数调用有递归,模板在展开的时候也可以有递归。在写递归函数时,我们需要找出递归的出口,这么不会陷入无限递归。同样,在运用模板展开时候递归同样需要考虑递归的出口。
为了实现随意log的输出,比如

    zz::logOld(1);
    zz::logOld(1, 2);
    zz::logOld(1, 2, '-', "Today");
    int a[4];
    zz::logOld("Hello", "World", 2016, '-', 3, '-', 19, a);

那么参数就是不可控的,但是在递归展开中,最后一个参数,肯定是递归的出口,如上面logOld函数定义所示。运行的效果:
这里写图片描述
但是,这种是递归实现,生成的代码也是嵌套的,性能方面有所损失,但是现实起来方便也很容易理解。

最近看了一些文章,关于可变参数模板的,有一些关于Parameter pack的文章,其中有关于Pack expansion的使用。
>

Pack expansion
A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the types from the pack, in order.

其就是提供一种多参数解包的模式,模式必须含有至少一个参数。

根据这个特性,我设计了以下的代码

template <typename T>
typename std::decay<T> unpack(const T& t)
{
    std::cout << t << ' ';
    typename std::decay<T> _ins;
    return _ins;
}

template <typename T, typename ... Args>
void debugLogImpl(const T& t, const Args& ... args)
{
    std::cout << t << ' ';
    auto _fun = [](...) {};
    _fun(unpack(args)...);
    std::cout << '\n';
}

unpack为解包一个参数的模式,unpack(args)…介绍多参数。
比如args为a,b,c,那么unpack(args)…等价于unpack(a), unpack(b), unpack(c)。这种形式的解包结果,我现在只想到了作为函数参数。所有我定义了一个lambda函数,其接受多参数,然后调用这个lambda函数来调用解包之后的unpack函数集合。
但是函数参数入栈的顺序是从右往左,那么先调用的是unpack(c),输出的也就是”c”,那么不行的。但是在C++17中我们可以直接,关于这个可以看下17中的fold expression

    (..., unpack(args);

根据原则

Unary left fold (… op E) becomes ((E1 op E2) op …) op EN

(…, unpack(args)会解包成((unpack(arg1) op unpack(arg2)) op …) op unpack(argN)

但是在C++11中,这是不可以的。我也没有找出这么顺序调用解包之后的函数的办法。为了解决这个log顺利不对的问题,无奈又写了一大段的特例模板。

template <typename T>
typename std::decay<T> unpack(const T& t)
{
    std::cout << t << ' ';
    typename std::decay<T> _ins;
    return _ins;
}

template <typename T, typename ... Args>
void debugLogImpl(const T& t, const Args& ... args)
{
    std::cout << t << ' ';
    auto _fun = [](...) {};
    _fun(unpack(args)...);
    std::cout << '\n';
}

template <typename T0>
void debugLog(const T0& t0)
{
    debugLogImpl(t0);
}

template <typename T0, typename T1>
void debugLog(const T0& t0, const T1& t1)
{
    debugLogImpl(t0, t1);
}

template <typename T0, typename T1, typename T2>
void debugLog(const T0& t0, const T1& t1, const T2& t2)
{
    debugLogImpl(t0, t2, t1);
}

template <typename T0, typename T1, typename T2, typename T3>
void debugLog(const T0& t0, const T1& t1, const T2& t2, const T3& t3)
{
    debugLogImpl(t0, t3, t2, t1);
}

...

template <typename T0, typename T1, typename T2, typename T3, typename T4
          , typename T5, typename T6, typename T7, typename T8, typename T9>
void debugLog(const T0& t0, const T1& t1, const T2& t2, const T3& t3, const T4& t4
              , const T5& t5 ,const T6& t6, const T7& t7, const T8& t8, const T9& t9)
{
    debugLogImpl(t0, t9, t8, t7, t6, t5,  t4, t3, t2, t1);
}

将参数调个个。

#define DEBUG_LOG(...) zz::debugLog(__VA_ARGS__)

定义一个宏,来统一对外口径。


int main()
{
//    zz::logOld(1);
//    zz::logOld(1, 2);
//    zz::logOld(1, 2, '-', "Today");
//    int a[4];
//    zz::logOld("Hello", "World", 2016, '-', 3, '-', 19, a);

    DEBUG_LOG(1);
    DEBUG_LOG(1, 2);
    DEBUG_LOG(1, 2, 3);
    DEBUG_LOG(1, '-', 'a', 1, "sssss");

    return 0;
}

结果如下
这里写图片描述

然后就是

template <typename T>
typename std::decay<T> unpack(const T& t)
{
    std::cout << t << ' ';
    typename std::decay<T> _ins;
    return _ins;
}

这边为什么要用decay,如果不用向这样

template <typename T>
T unpack(const T& t)
{
    std::cout << t << ' ';
    return t;
}

对于这样的“ssssss”,其会报

/home/zhou/work/qt_project/zzLog/main.cpp:48: error: no matching function for call to 'unpack(const char [6])'
     _fun(unpack(args)...);

---
/home/zhou/work/qt_project/zzLog/main.cpp:36: error: function returning an array
                     ^

使用decay将数组退化为const char*来解决问题。

更新于2016/5/8
有读者在评论中贴出了可以借助a dummy array,来更简单的展开表达式,真是酸爽啊,代码少的不是一行两行,这是在我你另一个开源项目中使用的logger。

DEFINE_NAMESPACE_MOOS_BEGIN


template <typename T>
void unpack(T&& t)
{
    std::cout << std::forward<T>(t) << ' ';
}

template <typename ... Args>
void debugLogImpl(Args&& ... args)
{
    int dummy[] = {0 , (unpack(std::forward<Args>(args)), 0)...};
    MOOS_UNUSE(dummy);
    std::cout << '\n';
}


template <typename ... Args>
void debugLog(Args&& ... args)
{
    debugLogImpl(std::forward<Args>(args)...);
}



DEFINE_NAMESPACE_MOOS_END


#define MOOS_DEBUG_LOG(...) Moos::debugLog("filename:", __FILE__, " line:", __LINE__, " ", __VA_ARGS__)

详细的关于 dummy array 可以看文献二。

参考文献:

[1]http://blog.csdn.net/yanxiangtianji/article/details/21045525#comments

[2]http://stackoverflow.com/questions/25680461/variadic-template-pack-expansion?answertab=votes#tab-top

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值