cmacrosclangc-preprocessor

C macro compilation fails in Clang with "expected ;" due to phantom comma in clang -E expansion


I wrote my "Result" macro header, using C23 and some of the newest features:

#pragma once

#include <stdio.h>
#include <stdlib.h>

#define __RESULT_EAT_PARENS(...) __VA_ARGS__

typedef enum ResultKind
{
    OK,
    ERR
} ResultKind;

#define DEFINE_RESULT(ok_type, err_type)         \
    typedef struct Result_##ok_type##_##err_type \
    {                                            \
        ResultKind kind;                         \
        union                                    \
        {                                        \
            ok_type ok;                          \
            err_type err;                        \
        } value;                                 \
    } Result_##ok_type##_##err_type;

#define __RESULT_OK_PASTE(ok_type, err_type, ...) \
    (Result_##ok_type##_##err_type)               \
    {                                             \
        .kind = OK, .value = {.ok = __VA_ARGS__ } \
    }

#define __RESULT_OK_IMPL(ok_type, err_type, ...) \
    __RESULT_OK_PASTE(ok_type, err_type, __VA_ARGS__)

#define __RESULT_ERR_PASTE(ok_type, err_type, ...)  \
    (Result_##ok_type##_##err_type)                 \
    {                                               \
        .kind = ERR, .value = {.err = __VA_ARGS__ } \
    }

#define __RESULT_ERR_IMPL(ok_type, err_type, ...) \
    __RESULT_ERR_PASTE(ok_type, err_type, __VA_ARGS__)

#define Ok(types, ...) \
    __RESULT_OK_IMPL(__RESULT_EAT_PARENS types, (__VA_ARGS__))

#define Err(types, ...) \
    __RESULT_ERR_IMPL(__RESULT_EAT_PARENS types, (__VA_ARGS__))

#define is_ok(res) \
    ((res).kind == OK)

#define is_err(res) \
    ((res).kind == ERR)

#define expect(res, msg)                                                                                        \
    ({                                                                                                          \
        __auto_type __res_tmp = (res);                                                                          \
        (is_err(__res_tmp))                                                                                     \
            ? (fprintf(stderr, "Panic at %s:%d: %s\n", __FILE__, __LINE__, (msg)), exit(1), __res_tmp.value.ok) \
            : __res_tmp.value.ok;                                                                               \
    })

#define expect_err(res, msg)                                                                                     \
    ({                                                                                                           \
        __auto_type __res_tmp = (res);                                                                           \
        (is_ok(__res_tmp))                                                                                       \
            ? (fprintf(stderr, "Panic at %s:%d: %s\n", __FILE__, __LINE__, (msg)), exit(1), __res_tmp.value.err) \
            : __res_tmp.value.err;                                                                               \
    })

#define unwrap_or(res, default_val)                            \
    ({                                                         \
        __auto_type __res_tmp = (res);                         \
        is_ok(__res_tmp) ? __res_tmp.value.ok : (default_val); \
    })

#define unwrap_or_else(res, func)                         \
    ({                                                    \
        __auto_type __res_tmp = (res);                    \
        is_ok(__res_tmp) ? __res_tmp.value.ok : (func)(); \
    })

#define __RESULT_MAP_PASTE(out_ok_type, err_type, res_in, var, ...)         \
    ({                                                                      \
        __auto_type __res_tmp = (res_in);                                   \
        (is_err(__res_tmp))                                                 \
            ? __RESULT_ERR_IMPL(out_ok_type, err_type, __res_tmp.value.err) \
            : ({                                                            \
                  typeof(__res_tmp.value.ok) var = __res_tmp.value.ok;      \
                  __RESULT_OK_IMPL(out_ok_type, err_type, __VA_ARGS__);     \
              });                                                           \
    })

#define __RESULT_MAP_IMPL(out_ok_type, err_type, res_in, var, ...) \
    __RESULT_MAP_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)

#define map(types, res_in, var, ...) \
    __RESULT_MAP_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

#define __RESULT_MAP_ERR_PASTE(ok_type, out_err_type, res_in, var, ...)   \
    ({                                                                    \
        __auto_type __res_tmp = (res_in);                                 \
        (is_ok(__res_tmp))                                                \
            ? __RESULT_OK_IMPL(ok_type, out_err_type, __res_tmp.value.ok) \
            : ({                                                          \
                  typeof(__res_tmp.value.err) var = __res_tmp.value.err;  \
                  __RESULT_ERR_IMPL(ok_type, out_err_type, __VA_ARGS__);  \
              });                                                         \
    })

#define __RESULT_MAP_ERR_IMPL(ok_type, out_err_type, res_in, var, ...) \
    __RESULT_MAP_ERR_PASTE(ok_type, out_err_type, res_in, var, __VA_ARGS__)

#define map_err(types, res_in, var, ...) \
    __RESULT_MAP_ERR_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

#define __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, ...)    \
    ({                                                                      \
        __auto_type __res_tmp = (res_in);                                   \
        (is_err(__res_tmp))                                                 \
            ? __RESULT_ERR_IMPL(out_ok_type, err_type, __res_tmp.value.err) \
            : ({                                                            \
                  typeof(__res_tmp.value.ok) var = __res_tmp.value.ok;      \
                  __VA_ARGS__                                               \
              });                                                           \
    })

#define __RESULT_AND_THEN_IMPL(out_ok_type, err_type, res_in, var, ...) \
    __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)

#define and_then(types, res_in, var, ...) \
    __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

Everything goes go well so for. But then I add:

 #define __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, ...)    \
    ({                                                                      \
        __auto_type __res_tmp = (res_in);                                   \
        (is_err(__res_tmp))                                                 \
            ? __RESULT_ERR_IMPL(out_ok_type, err_type, __res_tmp.value.err) \
            : ({                                                            \
                  typeof(__res_tmp.value.ok) var = __res_tmp.value.ok;      \
                  __VA_ARGS__                                               \
              });                                                           \
    })

#define __RESULT_AND_THEN_IMPL(out_ok_type, err_type, res_in, var, ...) \
    __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)

#define and_then(types, res_in, var, ...) \
    __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

I wrote a test to show the problem:

#include "result.h" // The header file with the macro definitions
#include <stdio.h>

// --- Type Definitions ---
typedef const char *CString;

// --- 1. Define the required Result type ---
// Result<int, CString>
DEFINE_RESULT(int, CString);

// --- Helper Functions for and_then ---

/**
 * @brief A function for the and_then test (Ok -> Ok or Ok -> Err)
 * Simulates an operation that might fail.
 */
Result_int_CString sophisticated_op(int i)
{
  printf("    -> (Called sophisticated_op(%d))\n", i);
  if (i > 10)
  {
    // Return a new Ok
    return Ok((int, CString), i * 2);
  }
  else
  {
    // Return an Err
    return Err((int, CString), "value too small");
  }
}

/**
 * @brief A function for the and_then test (Ok -> Err)
 */
Result_int_CString sophisticated_err_op(int i)
{
  printf("    -> (Called sophisticated_err_op(%d))\n", i);
  // Always returns Err
  return Err((int, CString), "sophisticated op failed");
}

// --- Main Test Function ---
int main()
{
  // --- Setup: Basic values ---
  // These macros (Ok, Err) work correctly.
  Result_int_CString res_ok = Ok((int, CString), 42);
  Result_int_CString res_err = Err((int, CString), "file not found");

  printf("\n--- 4. Chaining (and_then) ---\n");
  printf("Compilation is expected to fail on the following 3 lines:\n");

  // 4a. Ok(42) |> and_then(sophisticated_op) -> Ok(84)
  // COMPILATION FAILS HERE
  Result_int_CString chain1 = and_then(
      (int, CString),       // Target type (U, E)
      res_ok,               // Input Ok(42)
      val,                  // Bind variable
      sophisticated_op(val) // Expression -> Ok(84)
  );

  // 4b. Ok(42) |> and_then(sophisticated_err_op) -> Err("...")
  // COMPILATION FAILS HERE
  Result_int_CString chain2 = and_then(
      (int, CString), // Target type (U, E)
      res_ok,         // Input Ok(42)
      val,
      sophisticated_err_op(val) // Expression -> Err(...)
  );

  // 4c. Err(...) |> and_then(sophisticated_op) -> Err(...) (short-circuit)
  // COMPILATION FAILS HERE
  Result_int_CString chain3 = and_then(
      (int, CString),       // Target type (U, E)
      res_err,              // Input Err("file not found")
      val,                  // Bind variable
      sophisticated_op(val) // Expression - should not execute
  );

  printf("...If compilation somehow succeeded, printing results:\n");

  // These lines just print the results.
  // They require is_ok, unwrap_or, and expect_err from result.h.
  if (is_ok(chain1))
  {
    printf("and_then(Ok(42), op_ok): value = %d\n", unwrap_or(chain1, -1));
  }
  if (is_err(chain2))
  {
    printf("and_then(Ok(42), op_err): error = \"%s\"\n", expect_err(chain2, "should be err"));
  }
  if (is_err(chain3))
  {
    printf("and_then(Err, op_ok): error = \"%s\"\n", expect_err(chain3, "should be err"));
  }

  printf("All tests finished.\n");

  return 0;
}

I compiled it using clang and got this error:

karesis@Celestina:~/Projects/cprint$ clang -Wall -Wextra -std=c23 src/test_result.c -o test
src/test_result.c:141:31: error: expected ';' after expression
  141 |   Result_int_CString chain1 = and_then(
      |                               ^
src/result.h:134:5: note: expanded from macro 'and_then'
  134 |     __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
      |     ^
src/result.h:131:63: note: expanded from macro '__RESULT_AND_THEN_IMPL'
  131 |     __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)
      |                                                               ^
src/test_result.c:150:31: error: expected ';' after expression
  150 |   Result_int_CString chain2 = and_then(
      |                               ^
src/result.h:134:5: note: expanded from macro 'and_then'
  134 |     __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
      |     ^
src/result.h:131:63: note: expanded from macro '__RESULT_AND_THEN_IMPL'
  131 |     __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)
      |                                                               ^
src/test_result.c:159:31: error: expected ';' after expression
  159 |   Result_int_CString chain3 = and_then(
      |                               ^
src/result.h:134:5: note: expanded from macro 'and_then'
  134 |     __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
      |     ^
src/result.h:131:63: note: expanded from macro '__RESULT_AND_THEN_IMPL'
  131 |     __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)
      |                                                               ^
3 errors generated.
karesis@Celestina:~/Projects/cprint$ 

I tried running just the preprocessor (clang -Wall -Wextra -std=c23 -E test_result.c > expanded.txt) and saw the "ghost ,":

...
  Result_int_CString chain1 = ({ __auto_type __res_tmp = (res_ok); (((__res_tmp).kind == ERR)) ? (Result_int_CString) { .kind = ERR, .value = {.err = __res_tmp.value.err } } : ({ typeof(__res_tmp.value.ok) val = __res_tmp.value.ok; (sophisticated_op(val)), }); });
# 64 "test_mre.c"
  Result_int_CString chain2 = ({ __auto_type __res_tmp = (res_ok); (((__res_tmp).kind == ERR)) ? (Result_int_CString) { .kind = ERR, .value = {.err = __res_tmp.value.err } } : ({ typeof(__res_tmp.value.ok) val = __res_tmp.value.ok; (sophisticated_err_op(val)), }); });
# 73 "test_mre.c"
  Result_int_CString chain3 = ({ __auto_type __res_tmp = (res_err); (((__res_tmp).kind == ERR)) ? (Result_int_CString) { .kind = ERR, .value = {.err = __res_tmp.value.err } } : ({ typeof(__res_tmp.value.ok) val = __res_tmp.value.ok; (sophisticated_op(val)), }); });
...

It's like (using chain1 as example):

Result_int_CString chain1 =
    ({
        __auto_type __res_tmp = (res_ok);
        (
            ((__res_tmp).kind == ERR)
        )
        ? (Result_int_CString){
            .kind = ERR,
            .value = {.err = __res_tmp.value.err}
          }
        : ({
            typeof(__res_tmp.value.ok) val = __res_tmp.value.ok;
            (sophisticated_op(val)), /* <--- Problematic Comma Here */
          });
    });

I have no idea why there's a comma there. It is supposed to be a ; so I try to add a ;:

#define __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, ...)    \
    ({                                                                      \
        __auto_type __res_tmp = (res_in);                                   \
        (is_err(__res_tmp))                                                 \
            ? __RESULT_ERR_IMPL(out_ok_type, err_type, __res_tmp.value.err) \
            : ({                                                            \
                  typeof(__res_tmp.value.ok) var = __res_tmp.value.ok;      \
                  __VA_ARGS__; /* <--- I add a `;` */                       \
              });                                                           \
    })

But another bug occurred:

karesis@Celestina:~/Projects/cprint$ clang -Wall -Wextra -std=c23 test_mre.c -o test
test_mre.c:55:31: error: expected expression
   55 |   Result_int_CString chain1 = and_then(
      |                               ^
./result.h:132:5: note: expanded from macro 'and_then'
  132 |     __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
      |     ^
./result.h:129:5: note: expanded from macro '__RESULT_AND_THEN_IMPL'
  129 |     __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)
      |     ^
./result.h:124:30: note: expanded from macro '__RESULT_AND_THEN_PASTE'
  124 |                   __VA_ARGS__; /* <--- I add a `;` */                       \
      |                              ^
test_mre.c:64:31: error: expected expression
   64 |   Result_int_CString chain2 = and_then(
      |                               ^
./result.h:132:5: note: expanded from macro 'and_then'
  132 |     __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
      |     ^
./result.h:129:5: note: expanded from macro '__RESULT_AND_THEN_IMPL'
  129 |     __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)
      |     ^
./result.h:124:30: note: expanded from macro '__RESULT_AND_THEN_PASTE'
  124 |                   __VA_ARGS__; /* <--- I add a `;` */                       \
      |                              ^
test_mre.c:73:31: error: expected expression
   73 |   Result_int_CString chain3 = and_then(
      |                               ^
./result.h:132:5: note: expanded from macro 'and_then'
  132 |     __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
      |     ^
./result.h:129:5: note: expanded from macro '__RESULT_AND_THEN_IMPL'
  129 |     __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)
      |     ^
./result.h:124:30: note: expanded from macro '__RESULT_AND_THEN_PASTE'
  124 |                   __VA_ARGS__; /* <--- I add a `;` */                       \
      |                              ^
3 errors generated.
karesis@Celestina:~/Projects/cprint$ 

It seems that it actually needs a expression there. clang -E shows:

...
  Result_int_CString chain1 = ({ __auto_type __res_tmp = (res_ok); (((__res_tmp).kind == ERR)) ? (Result_int_CString) { .kind = ERR, .value = {.err = __res_tmp.value.err } } : ({ typeof(__res_tmp.value.ok) val = __res_tmp.value.ok; (sophisticated_op(val)),; }); });
# 64 "test_mre.c"
  Result_int_CString chain2 = ({ __auto_type __res_tmp = (res_ok); (((__res_tmp).kind == ERR)) ? (Result_int_CString) { .kind = ERR, .value = {.err = __res_tmp.value.err } } : ({ typeof(__res_tmp.value.ok) val = __res_tmp.value.ok; (sophisticated_err_op(val)),; }); });
# 73 "test_mre.c"
  Result_int_CString chain3 = ({ __auto_type __res_tmp = (res_err); (((__res_tmp).kind == ERR)) ? (Result_int_CString) { .kind = ERR, .value = {.err = __res_tmp.value.err } } : ({ typeof(__res_tmp.value.ok) val = __res_tmp.value.ok; (sophisticated_op(val)),; }); });
...

I checked my code again and again, but I found nothing wrong:

#pragma once

#include <stdio.h>
#include <stdlib.h>

#define __RESULT_EAT_PARENS(...) __VA_ARGS__

typedef enum ResultKind
{
    OK,
    ERR
} ResultKind;

#define DEFINE_RESULT(ok_type, err_type)         \
    typedef struct Result_##ok_type##_##err_type \
    {                                            \
        ResultKind kind;                         \
        union                                    \
        {                                        \
            ok_type ok;                          \
            err_type err;                        \
        } value;                                 \
    } Result_##ok_type##_##err_type;

#define __RESULT_OK_PASTE(ok_type, err_type, ...) \
    (Result_##ok_type##_##err_type)               \
    {                                             \
        .kind = OK, .value = {.ok = __VA_ARGS__ } \
    }

#define __RESULT_OK_IMPL(ok_type, err_type, ...) \
    __RESULT_OK_PASTE(ok_type, err_type, __VA_ARGS__)

#define __RESULT_ERR_PASTE(ok_type, err_type, ...)  \
    (Result_##ok_type##_##err_type)                 \
    {                                               \
        .kind = ERR, .value = {.err = __VA_ARGS__ } \
    }

#define __RESULT_ERR_IMPL(ok_type, err_type, ...) \
    __RESULT_ERR_PASTE(ok_type, err_type, __VA_ARGS__)

#define Ok(types, ...) \
    __RESULT_OK_IMPL(__RESULT_EAT_PARENS types, (__VA_ARGS__))

#define Err(types, ...) \
    __RESULT_ERR_IMPL(__RESULT_EAT_PARENS types, (__VA_ARGS__))

#define is_ok(res) \
    ((res).kind == OK)

#define is_err(res) \
    ((res).kind == ERR)

#define expect(res, msg)                                                                                        \
    ({                                                                                                          \
        __auto_type __res_tmp = (res);                                                                          \
        (is_err(__res_tmp))                                                                                     \
            ? (fprintf(stderr, "Panic at %s:%d: %s\n", __FILE__, __LINE__, (msg)), exit(1), __res_tmp.value.ok) \
            : __res_tmp.value.ok;                                                                               \
    })

#define expect_err(res, msg)                                                                                     \
    ({                                                                                                           \
        __auto_type __res_tmp = (res);                                                                           \
        (is_ok(__res_tmp))                                                                                       \
            ? (fprintf(stderr, "Panic at %s:%d: %s\n", __FILE__, __LINE__, (msg)), exit(1), __res_tmp.value.err) \
            : __res_tmp.value.err;                                                                               \
    })

#define unwrap_or(res, default_val)                            \
    ({                                                         \
        __auto_type __res_tmp = (res);                         \
        is_ok(__res_tmp) ? __res_tmp.value.ok : (default_val); \
    })

#define unwrap_or_else(res, func)                         \
    ({                                                    \
        __auto_type __res_tmp = (res);                    \
        is_ok(__res_tmp) ? __res_tmp.value.ok : (func)(); \
    })

#define __RESULT_MAP_PASTE(out_ok_type, err_type, res_in, var, ...)         \
    ({                                                                      \
        __auto_type __res_tmp = (res_in);                                   \
        (is_err(__res_tmp))                                                 \
            ? __RESULT_ERR_IMPL(out_ok_type, err_type, __res_tmp.value.err) \
            : ({                                                            \
                  typeof(__res_tmp.value.ok) var = __res_tmp.value.ok;      \
                  __RESULT_OK_IMPL(out_ok_type, err_type, __VA_ARGS__);     \
              });                                                           \
    })

#define __RESULT_MAP_IMPL(out_ok_type, err_type, res_in, var, ...) \
    __RESULT_MAP_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)

#define map(types, res_in, var, ...) \
    __RESULT_MAP_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

#define __RESULT_MAP_ERR_PASTE(ok_type, out_err_type, res_in, var, ...)   \
    ({                                                                    \
        __auto_type __res_tmp = (res_in);                                 \
        (is_ok(__res_tmp))                                                \
            ? __RESULT_OK_IMPL(ok_type, out_err_type, __res_tmp.value.ok) \
            : ({                                                          \
                  typeof(__res_tmp.value.err) var = __res_tmp.value.err;  \
                  __RESULT_ERR_IMPL(ok_type, out_err_type, __VA_ARGS__);  \
              });                                                         \
    })

#define __RESULT_MAP_ERR_IMPL(ok_type, out_err_type, res_in, var, ...) \
    __RESULT_MAP_ERR_PASTE(ok_type, out_err_type, res_in, var, __VA_ARGS__)

#define map_err(types, res_in, var, ...) \
    __RESULT_MAP_ERR_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

#define __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, ...)    \
    ({                                                                      \
        __auto_type __res_tmp = (res_in);                                   \
        (is_err(__res_tmp))                                                 \
            ? __RESULT_ERR_IMPL(out_ok_type, err_type, __res_tmp.value.err) \
            : ({                                                            \
                  typeof(__res_tmp.value.ok) var = __res_tmp.value.ok;      \
                  __VA_ARGS__; /* <--- I add a `;` */                       \
              });                                                           \
    })

#define __RESULT_AND_THEN_IMPL(out_ok_type, err_type, res_in, var, ...) \
    __RESULT_AND_THEN_PASTE(out_ok_type, err_type, res_in, var, __VA_ARGS__)

#define and_then(types, res_in, var, ...) \
    __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))karesis@Celestina:~/Projects/cprint$ 

Is this a weirdfeature of clang? (add , after a VAR__ARGS) or have I made a mistake? I would be extremely grateful for any help or insights!

MRE:

#include <stdio.h>

#define __RESULT_EAT_PARENS(...) __VA_ARGS__

#define PRINT_ARGS_MACRO(out_ok_type, err_type, res_in, var, ...) \
  printf("  arg 1 (out_ok_type): %s\n", #out_ok_type);            \
  printf("  arg 2 (err_type):    %s\n", #err_type);               \
  printf("  arg 3 (res_in):      %s\n", #res_in);                 \
  printf("  arg 4 (var):         %s\n", #var);                    \
  printf("  arg 5 (VA_ARGS):     %s\n", #__VA_ARGS__)

#define and_then_buggy(types, res_in, var, ...) \
  PRINT_ARGS_MACRO(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))

int main()
{
  printf("--- use macro ---\n");
  and_then_buggy(
      (int, CString), // types
      "res_ok",       // res_in
      "val",          // var
      "my_func(val)"  // ...
  );

  printf("\n--- expected ---\n");
  printf("  arg 1 (out_ok_type): int\n");
  printf("  arg 2 (err_type):    CString\n");
  printf("  arg 3 (res_in):      \"res_ok\"\n");
  printf("  arg 4 (var):         \"val\"\n");
  printf("  arg 5 (VA_ARGS):     (\"my_func(val)\")\n");

  return 0;
}

Solution

  • __VA_ARGS__ is empty, because types is expanded to the value (int, CString) and is eaten by out_ok_type in __RESULT_AND_THEN_IMPL. This happens because only a literal like int, CString is split into two values, but not a value containing a comma. __RESULT_EAT_PARENS does not help, because parenthesis are a part of a value in types and they do not invoke the macro with parameters. You would see it if you test the top macros individually.

    It's unclear why and_then is defined with 3 explicit parameters, and then use pass (int, CString). and_then should be defined with 4 explicit parameters.

    Simple test

    #define __RESULT_EAT_PARENS(...) __VA_ARGS__
    
    #define __RESULT_AND_THEN_IMPL(out_ok_type, err_type, res_in, var, ...) \
      printf("%s\n", #out_ok_type);                                         \
      printf("%s\n", #err_type);                                            \
      printf("%s\n", #res_in);                                              \
      printf("%s\n", #var);                                                 \
      printf("%s\n", #__VA_ARGS__)
    
    #define and_then(types, res_in, var, ...) \
      __RESULT_AND_THEN_IMPL(__RESULT_EAT_PARENS types, res_in, var, (__VA_ARGS__))
    
    int main() {
      and_then((int, CString),            // Target type (U, E)
               res_err,                   // Input Err("file not found")
               val,                       // Bind variable
               sophisticated_err_op(val)  // Expression -> Err(...)
      );
    }
    

    outputs

    __RESULT_EAT_PARENS (int, CString)
    res_err
    val
    (sophisticated_err_op(val))
    

    that is not what you want.

    You might want __RESULT_AND_THEN_IMPL with 3 explicit parameters.

    #define __RESULT_AND_THEN_IMPL(types, res_in, var, ...) \
      __RESULT_AND_THEN_PASTE(types, res_in, var, __VA_ARGS__)