5

When having arrays of structures I often miss the With-block approach I got used to in VB6 (similar to the Using-block in C#). For example, many of my code looks now like:

Data.attribute[i].ref->value[i]->member.val1 = 0;
Data.attribute[i].ref->value[i]->member.val2 = 2;
Data.attribute[i].ref->value[i]->member.val3 = 3;

While I liked to do something like:

with Data.attribute[i].ref->value[i]->member
{
    .val1 = 3;
    .val2 = 2;
    .val3 = 3;
}

I know I can create a temporary variabele, but is something like the above possible in C?

leppie
  • 115,091
  • 17
  • 196
  • 297
Maestro
  • 9,046
  • 15
  • 83
  • 116
  • 1
    No, it is not available. – OldProgrammer Feb 03 '13 at 15:20
  • 6
    Why are there downvotes on this? I know everyone, like, looks down on VB, but this a clear and specific question. – Ben Zotto Feb 03 '13 at 15:23
  • Long live the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). (Also, the two `i` subscripts are a little unexpected; normally, you'd need different array indexes for different arrays in a 6-part chain of references like that.) – Jonathan Leffler Feb 03 '13 at 15:57

3 Answers3

8
member_t* m = &(Data.attribute[i].ref->value[i]->member);
m->val1 = 3;
m->val2 = 2;
m->val3 = 3;

The above should work fine to shorten the line.

And since you use c99, you can even create those temporaries when you need them only.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    As i mentioned in my question, "I know I can create a temporary variabele", but I was looking for something fancier ;) – Maestro Feb 03 '13 at 15:22
  • @Joshua, I was overly eager :) I'm afraid that there isn't anything fancier in `c`. Too low level. – StoryTeller - Unslander Monica Feb 03 '13 at 15:24
  • 2
    @Joshua, I'm gonna hate myself for suggesting it, but you could wrap this up in a macro... – StoryTeller - Unslander Monica Feb 03 '13 at 15:25
  • 1
    Local variable is the way C does a `with` block. There is nothing fancier; there's no need for anything fancier. – Jonathan Leffler Feb 03 '13 at 15:59
  • 3
    In addition to shortening the line, it caches all the pointer arithmetic needed to find `Data.attribute[i].ref->value[i]->member`, so only needs to be done once, rather than three times. A micro-optimization the compiler may have already done, but still another note in favor of just using a local variable. If you're concerned about scope, you can just wrap the entire block in `{ ... }`, so `m` doesn't live any further than these four lines. – rampion Feb 03 '13 at 16:11
7

You can sorta fake this with macros, but it's not really worth it:

int main(){

  WITH(Data.attribute[i].ref->value[i]->member,
    .val1 = 3,
    .val2 = 2,
    .val3 = 3
  );
  return 0;
}

Where the WITH() macro is defined as a variadic macro that uses argument counting:

#define WITH(prefix, ...) do {\
  MACRO_CONCAT(_WITH,PP_NARG(__VA_ARGS__))(prefix, __VA_ARGS__);\
} while (0)

#define PP_NARG(...) \
           PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
           PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
              _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
             _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
             _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
             _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
             _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
             _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
             _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
           63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

#define _MACRO_CONCAT(a,b)  a ## b
#define MACRO_CONCAT(a,b)  _MACRO_CONCAT(a,b)

#define _WITH1(a,b)        a b
#define _WITH2(a,b, bs...) a b ; _WITH1(a, bs)
#define _WITH3(a,b, bs...) a b ; _WITH2(a, bs)
#define _WITH4(a,b, bs...) a b ; _WITH3(a, bs)
#define _WITH5(a,b, bs...) a b ; _WITH4(a, bs)
#define _WITH6(a,b, bs...) a b ; _WITH5(a, bs)
#define _WITH7(a,b, bs...) a b ; _WITH6(a, bs)
#define _WITH8(a,b, bs...) a b ; _WITH7(a, bs)
#define _WITH9(a,b, bs...) a b ; _WITH8(a, bs)
#define _WITH10(a,b, bs...) a b ; _WITH9(a, bs)
#define _WITH11(a,b, bs...) a b ; _WITH10(a, bs)
#define _WITH12(a,b, bs...) a b ; _WITH11(a, bs)
#define _WITH13(a,b, bs...) a b ; _WITH12(a, bs)
#define _WITH14(a,b, bs...) a b ; _WITH13(a, bs)
#define _WITH15(a,b, bs...) a b ; _WITH14(a, bs)
#define _WITH16(a,b, bs...) a b ; _WITH15(a, bs)
#define _WITH17(a,b, bs...) a b ; _WITH16(a, bs)
#define _WITH18(a,b, bs...) a b ; _WITH17(a, bs)
#define _WITH19(a,b, bs...) a b ; _WITH18(a, bs)
#define _WITH20(a,b, bs...) a b ; _WITH19(a, bs)
#define _WITH21(a,b, bs...) a b ; _WITH20(a, bs)
#define _WITH22(a,b, bs...) a b ; _WITH21(a, bs)
#define _WITH23(a,b, bs...) a b ; _WITH22(a, bs)
#define _WITH24(a,b, bs...) a b ; _WITH23(a, bs)
#define _WITH25(a,b, bs...) a b ; _WITH24(a, bs)
#define _WITH26(a,b, bs...) a b ; _WITH25(a, bs)
#define _WITH27(a,b, bs...) a b ; _WITH26(a, bs)
#define _WITH28(a,b, bs...) a b ; _WITH27(a, bs)
#define _WITH29(a,b, bs...) a b ; _WITH28(a, bs)
#define _WITH30(a,b, bs...) a b ; _WITH29(a, bs)
#define _WITH31(a,b, bs...) a b ; _WITH30(a, bs)
#define _WITH32(a,b, bs...) a b ; _WITH31(a, bs)
#define _WITH33(a,b, bs...) a b ; _WITH32(a, bs)
#define _WITH34(a,b, bs...) a b ; _WITH33(a, bs)
#define _WITH35(a,b, bs...) a b ; _WITH34(a, bs)
#define _WITH36(a,b, bs...) a b ; _WITH35(a, bs)
#define _WITH37(a,b, bs...) a b ; _WITH36(a, bs)
#define _WITH38(a,b, bs...) a b ; _WITH37(a, bs)
#define _WITH39(a,b, bs...) a b ; _WITH38(a, bs)
#define _WITH40(a,b, bs...) a b ; _WITH39(a, bs)
#define _WITH41(a,b, bs...) a b ; _WITH40(a, bs)
#define _WITH42(a,b, bs...) a b ; _WITH41(a, bs)
#define _WITH43(a,b, bs...) a b ; _WITH42(a, bs)
#define _WITH44(a,b, bs...) a b ; _WITH43(a, bs)
#define _WITH45(a,b, bs...) a b ; _WITH44(a, bs)
#define _WITH46(a,b, bs...) a b ; _WITH45(a, bs)
#define _WITH47(a,b, bs...) a b ; _WITH46(a, bs)
#define _WITH48(a,b, bs...) a b ; _WITH47(a, bs)
#define _WITH49(a,b, bs...) a b ; _WITH48(a, bs)
#define _WITH50(a,b, bs...) a b ; _WITH49(a, bs)
#define _WITH51(a,b, bs...) a b ; _WITH50(a, bs)
#define _WITH52(a,b, bs...) a b ; _WITH51(a, bs)
#define _WITH53(a,b, bs...) a b ; _WITH52(a, bs)
#define _WITH54(a,b, bs...) a b ; _WITH53(a, bs)
#define _WITH55(a,b, bs...) a b ; _WITH54(a, bs)
#define _WITH56(a,b, bs...) a b ; _WITH55(a, bs)
#define _WITH57(a,b, bs...) a b ; _WITH56(a, bs)
#define _WITH58(a,b, bs...) a b ; _WITH57(a, bs)
#define _WITH59(a,b, bs...) a b ; _WITH58(a, bs)
#define _WITH60(a,b, bs...) a b ; _WITH59(a, bs)
#define _WITH61(a,b, bs...) a b ; _WITH60(a, bs)
#define _WITH62(a,b, bs...) a b ; _WITH61(a, bs)
#define _WITH63(a,b, bs...) a b ; _WITH62(a, bs)
#define _WITH64(a,b, bs...) a b ; _WITH63(a, bs)

Check out the results of macro expansion using gcc -E:

int main(){

  do { 
    Data.attribute[i].ref->value[i]->member.val1 = 3;
    Data.attribute[i].ref->value[i]->member.val2 = 2; 
    Data.attribute[i].ref->value[i]->member.val3 = 3; 
  } while (0);

  return 0;
}

Note that you can use a macro and a local variable if you're willing to specify the type of the local variable (here assumed to be Member_t), and are using gcc-4.3 and above (or clang, or some other modernish compiler):

#define WITH_LOCAL(type, prefix, ...) _WITH_LOCAL( \
  type, \
  MACRO_CONCAT(__with_local, __COUNTER__), \
  prefix, \
  __VA_ARGS__ \
)
#define _WITH_LOCAL(type, local, prefix, ...) do {\
  type local = prefix;\
  MACRO_CONCAT(_WITH,PP_NARG(__VA_ARGS__))(local, __VA_ARGS__);\
} while(0)

int main(){

  WITH_LOCAL(Member_t *, &(Data.attribute[i].ref->value[i]->member),
    ->val1 = 3,
    ->val2 = 2,
    ->val3 = 3
  );

  return 0;
}

Which will expand to:

int main() {

  do { 
    Member_t * __with_local0 = &(Data.attribute[i].ref->value[i]->member);
    __with_local0->val1 = 3;
    __with_local0->val2 = 2;
    __with_local0->val3 = 3;
  } while(0);

  return 0;
}
rampion
  • 87,131
  • 49
  • 199
  • 315
  • 2
    +1: Interesting tour de force in macro hackery. Observation: the token pasting `##` isn't necessary since the strings being concatenated do not form parts of a single identifier; simple adjacency `a b` would suffice. According to the standard, '_If the result is not a valid preprocessing token, the behavior is undefined'._ Strictly, what you're producing is not a valid preprocessing token (it is a series of valid preprocessing tokens), so you're tending towards undefined behaviour. You may or may not get away with it; if you don't use the `##`, you're good to go. – Jonathan Leffler Feb 03 '13 at 16:08
  • using `#define MACRO_CONCAT(a,b) a b` or `#define _MACRO_CONCAT(a,b) a b` instead of the above definitons didn't work for me, since either expanded the use of `WITH(...)` above to `do { _WITH 3(...); } while (0)` according to `gcc -E`. So I'm going to disagree on the necessity of the `##`. Furthermore, why isn't the produced `_WITH3` a valid preprocessing token? – rampion Feb 03 '13 at 16:15
  • Ah, scratch that, you were referring to the use of `##` in the `_WITHN` defintions. Yeah, I'll fix that. – rampion Feb 03 '13 at 16:20
  • 1
    Did you test your code? (Yes, but maybe not with the stringent warning levels I use.) I copy/pasted your macro into "with.h" and the code into "main.c". I compiled with GCC 4.6.0 on Mac OS X with options `gcc -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -c main.c`. The critical error was: `main.c:9:1: error: pasting "member" and "." does not give a valid preprocessing token`, repeated three times. This is from the `a##b` part of the expansion, not the `##bs` part of the expansion, which is using a GCC extension. – Jonathan Leffler Feb 03 '13 at 16:23
  • 2
    I already upvoted this (because it blew my mind :) ), but I want to point out that with an extra type "hint", you can produce a block that uses a temporary object. – StoryTeller - Unslander Monica Feb 03 '13 at 16:28
  • 1
    Purely for your amusement, the following `main()` function compiles cleanly under the revised macros. The structure definition is intimidating, regardless of how many lines it is on. `int main(void) { int i = 2; struct data { struct attribute { struct ref { struct value { struct member { int val1; int val2; int val3; } member; } *value[4]; } *ref; } attribute[4]; } Data; WITH(Data.attribute[i].ref->value[i]->member, .val1 = 3, .val2 = 2, .val3 = 3); return 0; }` – Jonathan Leffler Feb 03 '13 at 16:34
  • StoryTeller: I was just working on that... :) (Took me a bit b/c I was using gcc-4.2, which is `__COUNTER__`-less, which confused me). – rampion Feb 03 '13 at 16:48
  • This is awesome trickery (+1!), but I'll be the curmudgeon that notes that the resulting C is far from idiomatic, and uninitiated other people reading your code won't really know what to make of it (since, of course, "with" scope isn't a C-native concept). So not sure I'd recommend its actual use in production projects. – Ben Zotto Feb 03 '13 at 18:14
0

In some cases you can use macros provided by C preprocessor. I guess these macros will be much more complicated than your current code, but sometimes it helps.

Josef Kufner
  • 2,851
  • 22
  • 28