"Intellisense", Macros, and Structs

Visual Studio just stole 2 hours of my life and I am going to rage about that a bit. Hopefully it means the next sucker gets a useful Google hit and doesn't have to dig as much.

So. Partly inspired by what Casey mentioned in last night's prestream, I decided to start using macros to define data structures I use a lot. In particular, dynamic arrays. And so I defined this macro:
1
2
3
#define DYNAMIC_ARRAY(type, name) \
    (type) * name ## s; \
    u32 name ## _count;

Pretty straightforward, nothing really exotic here. Just saves me the time and visual noise of writing out every pair of members.

When I go to use this, like so:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#define DYNAMIC_ARRAY(type, name) \
    (type) * name ## s; \
    u32 name ## _count;

struct ComponentSystem {
    DYNAMIC_ARRAY(TransformComponent, transform);
    DYNAMIC_ARRAY(ColliderComponent, collider);
    DYNAMIC_ARRAY(SpriteRendererComponent, sprite_renderer);
    DYNAMIC_ARRAY(CircleRendererComponent, circle_renderer);
};

Intellisense decides this is not acceptable, and generates a pageful of errors about "transform_count is not a member of ComponentSystem" and the like. That's a little odd, let's investigate. Autocomplete reports a single member in ComponentSystem: a method named DYNAMIC_ARRAY. I guess MSVC doesn't like macros inside struct definitions for some reason.

A bit of googling and digging around StackOverflow lead me to this MSDN page: https://msdn.microsoft.com/en-us/library/dd997977(v=vs.110).aspx

Okay, well that looks incredibly broken, but fine, let's do that. Create cpp.hint, add the macro definition. No change.

So now we create a small repro case:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
typedef unsigned int u32;

#define DYNAMIC_ARRAY(type, name) \
    (type) * name ## s; \
    u32 name ## _count;

struct Foo {
    DYNAMIC_ARRAY(int, thing);
};

#include <cstdio>

int main() {
    Foo foo = {};
    printf("%d\n", foo.thing_count);
    return 0;
}

This... doesn't trigger the behavior. Well isn't that interesting. Copy the problematic struct from before (with appropriate declarations for each type), and it *also* works just fine. But the original case is still broken.

I begin to suspect I may be accessing these incorrectly (the accessing is done in another bit of complicated macro handwaving, so this is plausible). So I create a global instance and try to access it... and the problem disappears. Both the original case and the new one work.

Okay, it's fixed, I can delete all the stuff I added and get back to work, right?

Of course, it immediately breaks again. Re-add the global instance and it fixes itself again.

What *seems* to be happening here is that once a file reaches a certain size (or certain number of symbols?), Intellisense decides to stop expanding macros in definitions. Not "put this off until I *need* the contents of this struct", it just won't, period. So that's great. Adding a static instance forces Intellisense to figure out the actual contents of the struct, and in the process fixes pointer access as well.

Gross hacky solution? Yes. But it shuts up Intellisense enough for me to get back to work. Probably won't be long, now, before I decide VS isn't worth the effort and switch to a simpler text editor.

And Microsoft? You owe me two hours. Cash or check.

EDIT:
A fix that doesn't require allocating a static struct:
1
static_assert(sizeof(ComponentSystem) != 0, "");

(It really is just about forcing the size calculation.)

Edited by Bryan Taylor on Reason: Found a better fix.
You never ever should trust Intellisense for warnings/errors. Always compile and then see what compiler says. MSVC intellisense uses different parser than actual compiler. No idea why they did that, but that's why you get different diagnostic messages from those two. This is something I learned long time ago when I started dealing with MSVC (I think around 2004).