提取__VA_ARGS__的第一个参数

4
假设我有一个宏:
#define FOO(a, ...) if (a) foo(a, ## __VA_ARGS__)

这个很好用:
  • FOO(a) 将被转换为 if (a) foo(a)
  • FOO(a, <some_parameters>) 将被转换为 if (a) foo(a, <some_parameters>)
是否可能修改此宏,仅将__VA_ARGS__的第一个参数(如果存在)传递给foo? 因此,我需要:
  • FOO(a) 被转换为 if (a) foo(a)
  • FOO(a, b, <some_parameters>) 被转换为 if (a) foo(a, b)
我尝试使用与BOOST_PP_VARIADIC_SIZE相同的想法来解决这个问题,但是结果这个宏对于BOOST_PP_VARIADIC_SIZE()(空参数)返回1,这不是预期的(我希望得到0)。
请注意,我需要一种解决方案,在bool(a)true时仅计算b<some_parameters>

1
可能是参数个数重载宏的重复问题。 - user4442671
@Frank:感谢你找到那个问题,基于那个问题我解决了这个问题。 - geza
你真的需要宏吗?简单的重载似乎可以胜任。 - Jarod42
@Jarod42:是的,需要使用宏,因为我需要惰性地评估 FOO 的参数(我的意思是,在实际代码中,只有在某些条件为真时才调用 foo。如果该条件为假,则不调用 foo,也不评估其参数)。 - geza
@geza,ab<some_parameters> 的类型都相同吗? - Hiroki
@Hiroki:不,它们是不同的。 - geza
2个回答

4
我建议使用带有通用lambda的可变参数宏作为解决方案。重要点如下:
  • It is difficult to pass both a and __VA_ARGS__ to a lambda as passed arguments in macro because when __VA_ARGS__ is empty

    [](){...}(a, __VA_ARGS__)
    

    becomes

    [](){...}(a,)
    

    and this , leads compilation error. Thus we split the first and second arguments of FOO into the captured and the passed ones respectively as follows. Then we can use a generic lambda in the macro even if __VA_ARGS__ is empty.

    [a](){...}(__VA_ARGS__)
    
  • The size of __VA_ARGS__ can be evaluated at compile-time as constexpr auto N. Then we can use if constexpr to separate function calls.

  • We can also apply if statement with initializer which is introduced from C++17 for if(a).

那么,建议的宏如下。

这对您也有效。

演示

#include <tuple>

#define FOO(a, ...)                                                           \
if(const bool a_ = (a); a_)                                                   \
[a_](auto&&... args)                                                          \
{                                                                             \
   const     auto t = std::make_tuple(std::forward<decltype(args)>(args)...); \
   constexpr auto N = std::tuple_size<decltype(t)>::value;                    \
                                                                              \
   if constexpr( N==0 ) {                                                     \
       return foo(a_);                                                        \
   }                                                                          \
   else {                                                                     \
       return foo(a_, std::get<0>(t));                                        \
   }                                                                          \
}(__VA_ARGS__)

谢谢!这种方法的问题在于args总是被评估。我需要一个解决方案,可以控制对args的评估。抱歉,在问题中我没有指定这一点(但评论中描述了这一点)。现在我会把这个信息放到问题里。但我还是会给你点赞 :) - geza
1
这是非常优雅的工作。出于好奇,为什么t不也是constexpr呢? - jonspaceharper
1
@JonHarper 谢谢。我认为 constexpr auto t 如果存在 __VA_ARGS__ 将会显示编译错误,因为 constexpr 变量必须保证在编译时初始化,而 args 不是通过常量表达式初始化的。我在这里找到了类似的讨论 here。 但是 argsN 的类型是在编译时确定的,因为通用 lambda 本质上与函数模板相同,它们是模板参数:https://dev59.com/lWQm5IYBdhLWcg3w6ShR 。 - Hiroki
@geza 自 C++17 引入的带初始化器的 if 语句对我来说似乎很有用。这个 链接 怎么样?如果我还是理解有误,能否指出问题 :) 。如果这提供了一个解决方案,我会编辑我的答案,无论您是否接受我的答案。 - Hiroki
@Hiroki:干得好!我感觉有点傻,因为周围的if语句很容易添加,我不知道为什么我没想到:) 请在这里添加if,我会接受你的答案! - geza

3

根据这个答案,我可以解决这个问题:

#define PRIVATE_CONCAT(a, b) a ## b

#define CONCAT(a, b) PRIVATE_CONCAT(a, b)

#define GET_100TH( \
    _01, _02, _03, _04, _05, _06, _07, _08, _09, _10,  \
    _11, _12, _13, _14, _15, _16, _17, _18, _19, _20,  \
    _21, _22, _23, _24, _25, _26, _27, _28, _29, _30,  \
    _31, _32, _33, _34, _35, _36, _37, _38, _39, _40,  \
    _41, _42, _43, _44, _45, _46, _47, _48, _49, _50,  \
    _51, _52, _53, _54, _55, _56, _57, _58, _59, _60,  \
    _61, _62, _63, _64, _65, _66, _67, _68, _69, _70,  \
    _71, _72, _73, _74, _75, _76, _77, _78, _79, _80,  \
    _81, _82, _83, _84, _85, _86, _87, _88, _89, _90,  \
    _91, _92, _93, _94, _95, _96, _97, _98, _99, PAR,  \
    ...) PAR

#define HAS_PARAMETER(...) GET_100TH(placeholder, ##__VA_ARGS__, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
        1, 1, 1, 1, 1, 1, 1, 1, 0)

#define FIRST_PARAMETER_WITH_PREPENDED_COMMA0(...)
#define FIRST_PARAMETER_WITH_PREPENDED_COMMA1(a, ...) , a

#define FIRST_PARAMETER_WITH_PREPENDED_COMMA(...) CONCAT(FIRST_PARAMETER_WITH_PREPENDED_COMMA, HAS_PARAMETER(__VA_ARGS__))(__VA_ARGS__)

#define FOO(a, ...) if (a) foo(a FIRST_PARAMETER_WITH_PREPENDED_COMMA(__VA_ARGS__))

这很丑陋...你确定要用这个毒害你的代码库吗? - YSC
1
@YSC:是的,它很丑。Boost的预处理器库也同样丑陋。但如果你有更好的解决方案,请随时与我们分享 :) - geza
1
@geza,你可以使用Python生成代码,而不是使用C++预处理器吗? :) - Yakk - Adam Nevraumont
@Yakk-AdamNevraumont:这个解决方案很好。即使它很丑陋,但界面还是可以的(我不在乎界面背后的丑陋。我关心的是界面和可用性。注释可以解释内部的丑陋)。使用Python完成这个任务是不必要的,会使构建更加复杂。但无论如何,还是谢谢你的想法 :) - geza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接