C 预处理器宏参数中的标记检测

Token detection within a C preprocessor macro argument

为了很好地修复 https://github.com/ned14/outcome/issues/244#issuecomment-774181015,我想知道是否可以检测 C 预处理器宏参数中是否存在标记序列?

#define OUTCOME_TRY_GLUE2(x, y) x##y
#define OUTCOME_TRY_GLUE(x, y) OUTCOME_TRY_GLUE2(x, y)
#define OUTCOME_TRY_UNIQUE_NAME OUTCOME_TRY_GLUE(unique, __COUNTER__)


#define OUTCOME_TRYV2_SUCCESS_LIKELY(unique, ...)      \
  auto &&unique = (__VA_ARGS__)                                                                                                                               

#define OUTCOME_TRY2_SUCCESS_LIKELY(unique, v, ...)    \
  OUTCOME_TRYV2_SUCCESS_LIKELY(unique, __VA_ARGS__);   \
  v = std::move(unique).value()

#define OUTCOME_TRYA(v, ...) OUTCOME_TRY2_SUCCESS_LIKELY(OUTCOME_TRY_UNIQUE_NAME, v, __VA_ARGS__)
 

/* I'd like this to generate:

auto unique0 = (expr); auto x = std::move(unique0).value();
*/
OUTCOME_TRYA(auto x, expr);

/* I'd like this to generate:

auto &&unique1 = (expr); auto &&x = std::move(unique1).value();
*/
OUTCOME_TRYA(auto &&x, expr);

https://godbolt.org/z/MW74cG 可能更有用)

这里需要实现的是在 v 宏参数的扩展中检测 &&,但如果它嵌套在 ()<>".

中则不需要检测

Whosebug 上的其他答案可以在 C 预处理器宏参数中找到一个字符串,但是这些技术在这里不起作用,因为 STRING_ ## && 的宏标记粘贴是不合法的。这种级别的 C 预处理器 hackery 超出了我的能力,所以我问 Whosebug。在此先感谢您的帮助。

What one needs to achieve here is detection of && within the expansion of the v macro argument, but not if it is nested within ()<>".

不,这是不可能的。我建议在单独的参数中将类型与名称分开。

OUTCOME_TRYA_NEW(auto, x, expr);
OUTCOME_TRYA_NEW(auto &&, x, expr);

您可以使用 stringify 将检测移动到运行时,例如:

#define OUTCOME_TRYA(v, ...) \
    if constexpr (your_constexpr_strstr(#v, "&&")) {  \
         /* do stuff with `auto &&` here */  \
    } else { \
         /* do stuff with `auto` here */  \
    }

(或者我认为还有一些 std::is_same(decltype(v)....) 的东西),但是当你想在宏扩展中声明变量时,我认为这没有帮助。

您可以使用的另一种方法是将第一个参数的扩展延迟到后面的阶段,这样您就可以在参数数量上重载它,如下所示:

// Add a comma. But later.
#define OUTCOME_REF(name)  &&, name

#define OUTCOME_TRYA_CHOOSE_1(name, ...)  \
        auto unique0 = (__VA_ARGS__); name = std::move(unique0).value();
#define OUTCOME_TRYA_CHOOSE_2_IN(name, ...) \
        auto &&unique1 = (__VA_ARGS__); name = std::move(unique1).value();

#define OUTCOME_TRYA_CHOOSE_2(name, ...) \
        OUTCOME_TRYA_CHOOSE_2_IN(name __VA_ARGS__)
//                               ^^^^^^ I have no idea why it works without a comma, but it does. It's scary.
#define OUTCOME_TRYA_CHOOSE_N(_1,_2,N,...) \
        OUTCOME_TRYA_CHOOSE_##N
#define OUTCOME_TRYA_CHOOSE(...) \
        OUTCOME_TRYA_CHOOSE_N(__VA_ARGS__,2,1)

#define OUTCOME_TRYA(name, ...) \
        OUTCOME_TRYA_CHOOSE(name)(name, __VA_ARGS__)
//                          ^^^^ - overload on number of arguments in expansion of first parameter   

OUTCOME_TRYA(auto x, expr1) // auto unique0 = (expr1); auto x = std::move(unique0).value();
OUTCOME_TRYA(auto OUTCOME_REF(x), expr2) // auto &&unique1 = (expr2); auto && x = std::move(unique1).value();

以类似的方式,您可以将界面无缝重构为:

#define UNPACK(...)  __VA_ARGS__
    
#define OUTCOME_TRYA_CHOOSE_1(name, nameunpacked, ...)  \
        auto unique0 = (__VA_ARGS__); name = std::move(unique0).value();
#define OUTCOME_TRYA_CHOOSE_2_IN(name1, name2, ...) \
        auto &&unique1 = (__VA_ARGS__); name1 name2 = std::move(unique1).value();

#define OUTCOME_TRYA_CHOOSE_2(name, nameunpacked, ...) \
        OUTCOME_TRYA_CHOOSE_2_IN(nameunpacked, __VA_ARGS__)
#define OUTCOME_TRYA_CHOOSE_N(_1,_2,_3,N,...) \
        OUTCOME_TRYA_CHOOSE_##N
#define OUTCOME_TRYA_CHOOSE(n, ...) \
        OUTCOME_TRYA_CHOOSE_N(n, __VA_ARGS__, 2, 1)

#define OUTCOME_TRYA(name, ...) \
        OUTCOME_TRYA_CHOOSE(name, UNPACK name)(name, UNPACK name, __VA_ARGS__)

OUTCOME_TRYA(auto x, expr1, expr2)
OUTCOME_TRYA((auto &&, x), expr3, expr4)

上面看起来不错,可以保留当前界面。诀窍在于 OUTCOME_TRYA_CHOOSE(name, UNPACK name) - 当 name(something, something) 时,则向 OUTCOME_TRYA_CHOOSE_N 传递 3 个参数,如果不是,则传递 2 个参数 - 然后可以重载参数的数量。因此,如果输入包含内部带有逗号的大括号,它会有效地重载。


I don't suppose you know of a way to achieve OUTCOME_TRYV((auto &&), expr1, expr2)

嗯 ;p 。解包时添加一个逗号,有效地将重载转移到一个。

#define UNPACK_ADD_COMMA(...)  ,__VA_ARGS__
    
#define OUTCOME_TRYA_CHOOSE_1(name, nameunpacked, ...)  \
        auto unique0 = (__VA_ARGS__); name = std::move(unique0).value();
#define OUTCOME_TRYA_CHOOSE_2_IN(name1, ...) \
        OCH_MY_GOD()
#define OUTCOME_TRYA_CHOOSE_3_IN(name1, name2, ...) \
        auto &&unique1 = (__VA_ARGS__); name1 name2 = std::move(unique1).value();


#define OUTCOME_TRYA_CHOOSE_2(name, nameunpacked, ...) \
        OUTCOME_TRYA_CHOOSE_2_IN(nameunpacked, __VA_ARGS__)
#define OUTCOME_TRYA_CHOOSE_3(name, ignore, nameunpacked, ...) \
        OUTCOME_TRYA_CHOOSE_3_IN(nameunpacked, __VA_ARGS__)
#define OUTCOME_TRYA_CHOOSE_N(_1,_2,_3,_4,N,...) \
        OUTCOME_TRYA_CHOOSE_##N
#define OUTCOME_TRYA_CHOOSE_IN(n, ...) \
        OUTCOME_TRYA_CHOOSE_N(n, __VA_ARGS__, 3, 2, 1)
#define OUTCOME_TRYA_CHOOSE(n, ...) \
        OUTCOME_TRYA_CHOOSE_IN(n, __VA_ARGS__)

#define OUTCOME_TRYA(name, ...) \
        OUTCOME_TRYA_CHOOSE(name, UNPACK_ADD_COMMA name)(name, UNPACK_ADD_COMMA name, __VA_ARGS__)

OUTCOME_TRYA(auto x, expr1, expr2)
OUTCOME_TRYA((auto &&, x), expr3, expr4)
OUTCOME_TRYA((auto &&), expr3, expr4)