top | item 11077970

(no title)

ryanprichard | 10 years ago

The example does not actually use compound literals. (i.e. _arg's initializer would work in C89.) It does use two other C99 features, though -- variadic macros and a for-loop-scoped variable declaration. The biggest compatibility issue, though, are its use of GCC extensions:

- Statement expressions: e.g.: int x = ({ int y = 0; y; });

- If zero-args are allowed (e.g. bar()), then _args is 0-sized, which is also non-standard.

These GCC extensions are somewhat common. Clang has them, and (I think) EDG's frontend does too. MSVC does not have either of them, even in MSVC 2015.

That said, a compound literal might be a good way to remove the extension use, but as long as bar() returns void, the do-while(0) trick is also sufficient for replacing the statement expression.

I think this compound literal usage works:

  #define bar(...) (bar( \
      sizeof((const char*[]) { NULL, __VA_ARGS__ }) / sizeof(const char*) - 1, \
      (const char*[]) { NULL, __VA_ARGS__ } + 1))
bar() results in trailing commas. My impression is that C99 allows them in array initializers. (I saw an example in the n1256 C draft.) I don't recall whether C89 also had them. __VA_ARGS__ is expanded twice, but I think that's OK, because sizeof() only evaluates its operand when it has VLA type, and it never will. This code will work in MSVC 2013 (if not earlier).

discuss

order

nkurz|10 years ago

I'm not familiar with MSVC, but when I tried this example:

  #include <stdio.h>

  void bar(int n, const char *p[]) {
    for (int i = 0; i < n; i++) {
      printf("%s\t", p[i]);
    }
    putchar('\n');
  }

  #define bar(...) (bar( \
  sizeof((const char*[]) { NULL, __VA_ARGS__ }) / sizeof(const char*) - 1, \
  (const char*[]) { NULL, __VA_ARGS__ } + 1))

  int main(/* int argc, char **argv */) {
    bar();
    bar("a");
    bar("a", "b");
    bar("a", "b", "c");
    return 0;
  }
Using the online MSVC compiler at http://webcompiler.cloudapp.net/ (which runs Visual C++ 19.00.23720.0), it failed with these error message:

  Compiled with  /EHsc /nologo /W4 /c
  main.cpp
  main.cpp(20): error C4576: a parenthesized type followed by    an 
  initializer list is a non-standard explicit type conversion syntax
  main.cpp(21): error C4576: a parenthesized type followed by an initializer
  list is a non-standard explicit type conversion syntax
  main.cpp(22): error C4576: a parenthesized type followed by an initializer list
  is a non-standard explicit type conversion syntax
  main.cpp(23): error C4576: a parenthesized type followed by an initializer list
  is a non-standard explicit type conversion syntax

nkurz|10 years ago

If you don't need the return value, the do-while approach works as you suggested:

  #define bar(...) do {		                   	 \
	const char *_args[] = {NULL, __VA_ARGS__};       \
	bar(sizeof(_args)/sizeof(*_args) - 1, _args + 1);\
  } while (0)
If for some absurd reason you really do need to emulate a "compound statement expression" in MSVC (because you need to declare some temp variables in a macro that can be used for assignment), it turns out that it can be done with a "lambda expression":

  #include <stdio.h>

  int bar(int n, const char *p[])
  {
    int total = 0; 
    for (int i = 0; i < n; i++) {
      total += printf("%s\t", p[i]);
    }
    putchar('\n');
    return total;
  }

  #define bar(...) [](){                                       \
    const char *_args[] = {NULL, __VA_ARGS__};                 \
    return bar(sizeof(_args)/sizeof(*_args) - 1, _args + 1);   \
  }()

  int main(/* int argc, char **argv */) {
    int total = 0;
    total += bar();
    total += bar("a");
    total += bar("a", "b");
    total += bar("a", "b", "c");
    printf("total: %d\n", total);
    return 0;
  }

ryanprichard|10 years ago

Compound literals were never formally added to C++.

Still, that hasn't stopped various front-ends (including GCC and Clang) from adding them to their C++ dialects. My code compiles with Clang, but not GCC. GCC complains that it's not OK to take the address of the array compound literal.

FWIW, C99 makes it clear that the object has automatic storage duration lasting until the end of the enclosing block. i.e. There shouldn't be an issue with dangling pointers.

PeCaN|10 years ago

I didn't realize statement expressions were a GCC extension.

How do you do the safe min/max in MSVC? e.g.

    #define min(a,b) ({ \
      __typeof__(a) _a = (a); \
      __typeof__(b) _b = (b); \
      _a > _b ? _b : _a; \
    })
works well in GCC/Clang. MSVC has decltype(a) _a instead of__typeof__, but I didn't know it doesn't have blocks-as-expressions.

ryanprichard|10 years ago

My impression is that it's impossible in MSVC's C dialect today. (Obviously there's std::min in C++.)

AFAICT, it could be done using _Generic and inline functions, if/when MSVC adds _Generic. I'd expect to see _Generic before seeing any of the GCC extensions, but I'm not aware of any commitment from MS to add it. There are some issues with _Generic and qualifiers that might slow its adoption. e.g. There's an open defect report regarding qualified rvalues (DR423), but even lvalues seem to behave differently between GCC 5.2.0 and Clang 3.7.1. GCC ignores an lvalue's qualifiers but still allows a "const int" branch. AFAICT, a const-qualified branch is never selected with GCC?

I'm wondering whether it's possible to use _Generic to require that the left and right operands have the same type (either before or after integral promotions). Qualifiers are an obvious nuisance, but the bigger problem is that even the unselected branches must still compile, and C does not have tuples.

ksherlock|10 years ago

MSVC is a c++ compiler so you should probably just use a templated inline function.