1

In order to nicely fix https://github.com/ned14/outcome/issues/244#issuecomment-774181015, I want to know if it is possible to detect whether a token sequence is present within a C preprocessor macro argument?

#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 may be more useful)

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

Other answers on Stackoverflow can find a string within a C preprocessor macro argument, however those techniques can't work here because macro token paste of STRING_ ## && isn't legal. This level of C preprocessor hackery is beyond my capabilities, so I ask Stackoverflow. Thanks in advance for any help.

1 Answer 1

2

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

No, it is not possible. I suggest separating the type from the name in a separate argument.

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

You could move the detection to runtime with stringify, like:

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

(or I think also with some std::is_same(decltype(v)....) stuff), but as you want to declare variables in the macro expansion, I think that's not helpful.

Another way you could use is to delay the expansion of first argument to later phase so you could overload it on number of arguments, like so:

// 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();

In similar fashion you could refactor your interface seamlessly to:

#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)

The above looks nice and allows for the current interface to stay. The trick is in OUTCOME_TRYA_CHOOSE(name, UNPACK name) - when name is (something, something), then OUTCOME_TRYA_CHOOSE_N is passed 3 arguments, if it's not, then 2 arguments are passed - which can be then overloaded on number of arguments. So it effectively overloads if the input contains braces with a comma inside.


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

Meh ;p . Add a comma when unpacking, effectively shifting overloads to one more.

#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)
Sign up to request clarification or add additional context in comments.

6 Comments

I tried this myself during the weekend, multiple techniques including magic reflection via aggregate initialisation of POD types, and I always ran into a roadblock of some form. I'm going to accept this as the answer, and thanks for giving it a try.
@NiallDouglas Please take a look at the edit. :p
Firstly thanks so much for the idea that (...) would tag a different overload! I don't suppose you know of a way to achieve OUTCOME_TRYV((auto &&), expr1, expr2) i.e. to select a different macro paste if, and only if, the first macro parameter has brackets around it? I've achieved it if the first macro parameter is two items i.e. (auto &&, x) but I've not figured out a way if it's a single item.
Apply a macro on it that adds a comma. Och uff, a sec :p Basically like #define ADD_COMMA(...) __VA_ARGS__, ignore #define TRYV(x, ...) OVERLOAD_ME(x, ADD_COMMA x) - when ADD_COMMA will expand, it will add a comma, that can be checked inside OVERLOAD_ME. If it's not, it will get two args.
Thanks for the further ideas. In the end, I gave up on persuading MSVC's broken preprocessor to behave, and I ultimately chose to add a new macro for when you want to specify OUTCOME_TRYV with a non-deduced unique. You can see examples at github.com/ned14/outcome/blob/issue0244/test/tests/…. I'll be thanking you in the Outcome release notes for your help, you were very helpful in this, thank you!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.