https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101488
--- Comment #1 from Jakub Jelinek <jakub at gcc dot gnu.org> --- I've tried a WIP (with just 0 && or commenting out parts of current code instead of removing/cleaning up) that attempts to treat __VA_OPT__ ( ... ) with ## before __VA_OPT__ and/or after ) more similarly to the case where the __VA_OPT__, ( and ) tokens are missing: --- libcpp/macro.c.jj 2021-07-16 11:10:08.512925510 +0200 +++ libcpp/macro.c 2021-07-17 15:55:03.178568895 +0200 @@ -2059,7 +2059,7 @@ replace_args (cpp_reader *pfile, cpp_has /* Remove any tail padding from inside the __VA_OPT__. */ paste_flag = tokens_buff_last_token_ptr (buff); - while (paste_flag && paste_flag != start + while (0 && paste_flag && paste_flag != start && (*paste_flag)->type == CPP_PADDING) { tokens_buff_remove_last_token (buff); @@ -2184,7 +2184,7 @@ replace_args (cpp_reader *pfile, cpp_has previous emitted token is at the beginning of __VA_OPT__; placemarkers within __VA_OPT__ are ignored in that case. */ else if (arg_tokens_count == 0 - && tmp_token_ptr != vaopt_start) +/* && tmp_token_ptr != vaopt_start */) paste_flag = tmp_token_ptr; } } @@ -2197,7 +2197,7 @@ replace_args (cpp_reader *pfile, cpp_has MACRO_ARG_TOKEN_EXPANDED, arg, arg->expanded); - if (last_token_is (buff, vaopt_start)) + if (0 && last_token_is (buff, vaopt_start)) { /* We're expanding an arg at the beginning of __VA_OPT__. Skip padding. */ @@ -2215,7 +2215,7 @@ replace_args (cpp_reader *pfile, cpp_has /* Padding on the left of an argument (unless RHS of ##). */ if ((!pfile->state.in_directive || pfile->state.directive_wants_padding) && src != macro->exp.tokens && !(src[-1].flags & PASTE_LEFT) - && !last_token_is (buff, vaopt_start)) + /* && !last_token_is (buff, vaopt_start) */) { const cpp_token *t = padding_token (pfile, src); unsigned index = expanded_token_index (pfile, macro, src, i); @@ -2301,7 +2301,7 @@ replace_args (cpp_reader *pfile, cpp_has /* Avoid paste on RHS (even case count == 0). */ if (!pfile->state.in_directive && !(src->flags & PASTE_LEFT) - && !last_token_is (buff, vaopt_start)) + /*&& !last_token_is (buff, vaopt_start)*/) { const cpp_token *t = &pfile->avoid_paste; tokens_buff_add_token (buff, virt_locs, @@ -3544,6 +3544,8 @@ create_iso_definition (cpp_reader *pfile bool varadic = false; bool ok = false; cpp_macro *macro = NULL; + bool vaopt_after_paste = false; + unsigned int vaopt_end_idx = 0; /* Look at the first token, to see if this is a function-like macro. */ @@ -3700,14 +3702,33 @@ create_iso_definition (cpp_reader *pfile token[-1].flags |= SP_DIGRAPH; if (token->flags & PREV_WHITE) token[-1].flags |= SP_PREV_WHITE; + if (macro->count == vaopt_end_idx) + token[-2].flags |= PASTE_LEFT; } following_paste_op = true; } else following_paste_op = false; - if (vaopt_tracker.update (token) == vaopt_state::ERROR) + vaopt_state::update_type vostate = vaopt_tracker.update (token); + if (vostate == vaopt_state::INCLUDE) + continue; + if (vostate == vaopt_state::ERROR) goto out; + if (vostate == vaopt_state::BEGIN + && macro->count > 1 + && (token[-1].flags & PASTE_LEFT)) + vaopt_after_paste = true; + if (vostate == vaopt_state::DROP && vaopt_after_paste) + { + vaopt_after_paste = false; + /* Duplicate the PASTE_LEFT flag on ( after __VA_OPT__ + if ## is before __VA_OPT__. */ + if (!vaopt_tracker.stringify ()) + token->flags |= PASTE_LEFT; + } + if (vostate == vaopt_state::END) + vaopt_end_idx = macro->count; } /* We're committed to winning now. */ and that results into identical output on the #c0 testcase above as clang++, and ditto macro_vaopt_p1042r1.cpp testcase, but macro_vaopt_expand.cpp differs slightly: -9: HT_A() C ( ) B ( ) "A()" +9: TONG C ( ) B ( ) "A()" -27_1: BA0 11 +27_1: BexpandedA0 11 (- is gcc with #__VA_OPT__ and the above patch, + is clang++). That actually matches how the F0 case in #define LPAREN ( #define A0 expandedA0 #define A1 expandedA1 A0 #define A2 expandedA2 A1 #define A3 expandedA3 A2 #define A() B LPAREN ) #define B() C LPAREN ) #define C() D LPAREN ) #define HT_B() TONG #define F0(x, ...) HT_ ## x x A() #x #define F(x, ...) HT_ ## __VA_OPT__(x x A() #x) 8: F0(A(),1) 9: F(A(),1) is handled, so I'm getting lost on what is supposed to be special about __VA_OPT__ here. In any case, the unpatched libcpp handled that F0 and F cases the same as clang++. And we probably need more extensive testsuite coverage...