是否可以创建一个生成转发函数的可变宏?

时间:2018-01-13 15:53:34

标签: c++ c-preprocessor

我很确定这个问题的答案是" no"但我很高兴被证明是错的

假设我有一个可变参数模板函数

template <typename... Args>
void log(Args&&... args) { ... }

...我还有用于生成具体日志记录功能的宏。像:

#define LOG_2(fname, arg0_type, arg0_name, arg1_type, arg1_name) \
  void fname(arg0_type arg0_name, arg1_type arg1_name) { \
    log(std::forward<arg0_type>(arg0_name), std::forward<arg1_type>(arg1_name)); }

也存在名为LOG_0LOG_10且代码类似的宏。

我使用它们来生成特殊的日志记录功能,这有两个目的。首先,它们为参数提供强类型,然后为它们提供名称,其中代码完成凸轮用作提示。例如:

LOG_2(progress_log, const char*, msg, float, part)
...
progress_log("Loading: ", complete/total);

然后除了args的强类型之外,我得到以下很好的代码完成提示:

code completion hint

那么,是否可以在宏中删除显式arity并编写具有完全相同效果但使用可变参数宏的东西? ...并且也不是宏,它使用计数器通过将适当的数字与LOG_连接来调用其中一个底层arity-macros。

我知道我可以计算参数,但大多数代码完成工具完全无法扩展计数宏,特别是如果可能的参数计数很大(如10)。因此代码完成效果会丢失。如果我愿意放弃代码完成提示效果,那么我宁愿选择类似LOG_FUN(progress_log, void(const char*, float))的东西,它也可以用来生成编译错误,但是没有参数名称作为提示

2 个答案:

答案 0 :(得分:1)

Boost.Preprocessor库是为了这种情况而制作的,当你真的想要用预处理器宏做一些奇特的事情时。但它可能会有点难看。

#include <boost/preprocessor/variadic/to_seq.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/arithmetic.hpp>
#include <boost/preprocessor/punctuation.hpp>
#include <boost/preprocessor/logical.hpp>
#include <utility>

template <typename... Args>
void log(Args&&...) {}

#define DEF_LOG_COMMA_BEFORE_PAIR(i) \
  BOOST_PP_COMMA_IF(BOOST_PP_AND(i, BOOST_PP_NOT(BOOST_PP_MOD(i,2))))

#define DEF_LOG_EXPAND_PARAM(r, data, i, elem) \
  DEF_LOG_COMMA_BEFORE_PAIR(i) elem

#define DEF_LOG_EXPAND_CALL(r, data, i, elem) \
  DEF_LOG_COMMA_BEFORE_PAIR(i) \
  BOOST_PP_IF( BOOST_PP_MOD(i,2), \
    (elem), \
    std::forward<elem> )

#define DEF_LOG_FUNC(name, ...) \
  inline void name( \
    BOOST_PP_SEQ_FOR_EACH_I(DEF_LOG_EXPAND_PARAM, ~, \
      BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
  ) { log( \
    BOOST_PP_SEQ_FOR_EACH_I(DEF_LOG_EXPAND_CALL, ~, \
      BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
  ); }

DEF_LOG_FUNC(progress_log, const char*, msg, float, part)

int main() {
    progress_log("Progress", 0.25);
}

要将其拆开,请先查看DEF_LOG_COMMA_BEFORE_PAIR宏。如果参数i是一个非零的偶数,它只是一个扩展为逗号的助手,否则扩展为空。通过将它放在转换一个vararg参数的结果之前,我们每隔一个项目得到一个逗号。 (之后可能看起来更自然,但是在最后一个术语之后排除逗号会比在第一个项目之前排除逗号的方式稍微复杂一些。)注意BOOST_PP_COMMA_IF用于避免扩展为逗号太早了,这会混淆其他Boost宏。

跳到主宏DEF_LOG_FUNC的定义,您会看到它使用BOOST_PP_SEQ_FOR_EACH_I两次。这是一个工具,它将提供的宏应用于传递的参数序列中的每个术语。给定的宏必须接受四个参数:

  • r:当您需要多级预处理器循环时使用

  • data:一系列令牌直接通过BOOST_PP_SEQ_FOR_EACH_I传递给宏的每次调用

  • i:序列元素的从零开始的索引

  • elem:实际的序列元素

我们的任何一个宏都不需要data,所以我们只将~作为虚拟标记传递给BOOST_PP_SEQ_FOR_EACH_I两个用户。传递给BOOST_PP_SEQ_FOR_EACH_I的两个辅助宏是DEF_LOG_EXPAND_PARAM用于构建函数参数列表,DEF_LOG_EXPAND_CALL用于构建参数列表到实际的log函数。 DEF_LOG_EXPAND_PARAM除了使用DEF_LOG_COMMA_BEFORE_PAIR帮助器添加逗号外,无需执行任何操作。 DEF_LOG_EXPAND_CALL需要对偶数和奇数参数做不同的事情,因此除了DEF_LOG_COMMA_BEFORE_PAIR工具之外,它还使用BOOST_PP_IF将偶数(类型)参数转换为std::forward<elem>(elem)的奇数(参数)参数。

根据需要,预处理器替换

DEF_LOG_FUNC(progress_log, const char*, msg, float, part)

作为

inline void progress_log(
    const char* msg , float part
) { log(
    std::forward<const char*> (msg) , std::forward<float> (part)
); }

答案 1 :(得分:0)

您可以改用一些标签。如果您需要提供特殊情况的提示,那么您可以编写一些明确的专业化:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<ul class="filter-nav"></ul>