3

I want to have a function that prints out information about a member variable of a struct. In order to keep the function as simple (and error free) as possible I dont want to manually pass in the type as well. This causes me to need to be able to evaluate the arguments passed into my macro:

#ifndef preprocessor_stringify
#define preprocessor_stringify(s) #s
#endif

typedef struct test_s {
    void (*ptr)(void*);
} test;

void doSomething_(char *name, int offset, int size){
    printf("%s %d %d\n", name, offset, size);
}

#define doSomething(name, container) (\
    doSomething_(\
        preprocessor_stringify(name),\
        offsetof(container, name),\
        sizeof(container->name))\
    );

int main(){
    doSomething(ptr, test);
    return 0;
}

This yields a compile error of test.cpp:21:19: error: expected primary-expression before ‘->’ token sizeof(container->name))\

Any ideas on how to fix this? I would like the solution to be both c and c++ compatible, ideally.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
chacham15
  • 13,719
  • 26
  • 104
  • 207
  • I think it should be `container::ptr` because `container` is actually a type name, not a pointer. – user7860670 Jul 17 '17 at 15:48
  • Ah, great point. I forgot to mention that I would prefer a solution which works with both c and c++ (hence both tags on the question). EDIT: on second read, are you saying that this isnt possible with straight c? – chacham15 Jul 17 '17 at 15:49
  • In C it is not really possible, see [this question](https://stackoverflow.com/questions/539251/getting-the-size-of-an-indiviual-field-from-a-c-struct-field). – user7860670 Jul 17 '17 at 15:53
  • You might be able to get away with `sizeof(((container *)0)->name)`, but ugly doesn't begin to describe it. And it is possible in C (and C++), but it is not nice in either. – Jonathan Leffler Jul 17 '17 at 15:54
  • Sidenote: To make macro behave more like an statement, you should wrap it in `do { ... } while(0)` (without semicolon), instead of `( ... );`. – user694733 Jul 17 '17 at 15:56
  • 2
    @vtt: the question you link is about bitfields. How is that relevant? – rici Jul 17 '17 at 15:58
  • @rici That question however contains cheesy method with getting size of member through casting a null, that others posted here as an answer... – user7860670 Jul 17 '17 at 16:02
  • 1
    @vtt Where in the C++ post you [reference](https://stackoverflow.com/questions/45148437/how-can-i-evaluate-an-argument-in-a-preprocessor-macro-to-pass-to-sizeof#comment77266337_45148437) supports " In C it is not really possible"? – chux - Reinstate Monica Jul 17 '17 at 16:03
  • @chux Alright: In C it is not really possible without creating temporary objects or using cheesy methods involving differencing of null pointer. And in general case it is not possible in both C and C++ as well when we are dealing with bit fields. – user7860670 Jul 17 '17 at 16:10
  • 1
    @VTT Since you can't apply `sizeof` to a bit-field at all, complaints about it not working are misguided. – Jonathan Leffler Jul 17 '17 at 16:17
  • 2
    @VTT: Every programmer is entitled to their aesthetic sensibilities, and arguably you cannot be a good programmer without them. However, if you program in C (or any real-world programming language), you will need to set them aside from time to time. At least, you need to be able to differentiate between constructs which don't appeal to you, and constructs which are not well-formed or otherwise have unpredictable effects. The NULL cast to get the size of a member is a perfectly legal and well-defined construct according to the language definition, so it certainly is possible. – rici Jul 17 '17 at 21:17

2 Answers2

6
#include <stdio.h>
#include <stddef.h>

#ifndef preprocessor_stringify
#define preprocessor_stringify(s) #s
#endif

typedef struct test_s {
    void (*ptr)(void*);
} test;

void doSomething_(char const *name, int offset, int size){
    printf("%s %d %d\n", name, offset, size);
}

#define doSomething(name, container) (\
    doSomething_(\
        preprocessor_stringify(name),\
        offsetof(container, name),\
        sizeof(((container*)0)->name))\
    );

int main(){
    doSomething(ptr, test);
    return 0;
}

I have made two changes:

  1. In c++, string literals are const char[]

 

void doSomething_(char const *name, int offset, int size){
  1. We want the sizeof a model object, so we have to make a model:

 

sizeof(((container*)0)->name))\

One of the comments mentioned that the pointer conversion is ugly. I agree, let's confine it to one macro which we can re-use.

#define sizeof_member(Class, Member) sizeof ((Class*)0)->Member

#define doSomething(name, container) (\
    doSomething_(\
        preprocessor_stringify(name),\
        offsetof(container, name),\
        sizeof_member(container, name)) \
    );
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
-1

Another alternative is to use temporary object.

#define doSomething(name, container)      \
    do {                                  \
        container temp;                   \
        doSomething_(                     \
            preprocessor_stringify(name), \
            offsetof(container, name),    \
            sizeof(temp.name)             \
        );                                \
    } while(0)

It's very likely that object is optimized away. (Might not apply to complex C++ objects).

user694733
  • 15,208
  • 2
  • 42
  • 68
  • This requires that the container has a default constructor. Also, the compiler doesn't have to optimize the object away. – Kevin Jul 17 '17 at 16:05
  • @Kevin True, but given OPs C compatibility requirement, constructor requirement should not be an issue. Also, optimization should be trivial to any half-smart compiler. – user694733 Jul 17 '17 at 16:07