3

I'm writing a code generator, well, actually a data generator that will produce data structures of this form (obviously the actual data structures are much more elaborate):

typedef struct Foo {
  int a;
  struct Foo* foo;
} Foo;

extern Foo f1;
extern Foo f2;

Foo f1 = {1, &f2};
Foo f2 = {2, &f1};

This is portable for all C and C++ compilers I have tried.

I would like to forward declare these struct instances as static so as not to pollute the global variable space, as in:

typedef struct Foo {
  int a;
  struct Foo* foo;
} Foo;

static Foo f1;
static Foo f2;

static Foo f1 = {1, &f2};
static Foo f2 = {2, &f1};

Although this works with gcc and probably all C compilers, the above code does not work with C++ compilers and results in a compile error:

error: redefinition of ‘Foo f1’
error: ‘Foo f1’ previously declared

I understand why this is happening in C++. Is there a simple workaround that does not involve using code at runtime to achieve the same effect that is portable to all C++ compilers without resorting to using a C compiler to compile certain files?

  • 1
    Maybe I'm just being dense, but what are you trying to achieve? You're not forward declaring anything, you're creating variables of type Foo, several times with the same name. But I'm not really sure why. Why not just declare each variable once? – jalf Feb 28 '09 at 17:48
  • You can't declare the variables once because they are cross referenced: static Foo f1 = {1, &f2}; static Foo f2 = {2, &f1}; - you have to forward declare. –  Feb 28 '09 at 17:59

7 Answers7

7

This should compile with either C or C++ and give you the same name to access the same thing in both compilers.

#ifdef __cplusplus
 namespace // use anonymous namespace to avoid poluting namespace.
 {
    struct StaticFoos
    {
       static Foo f1;
       static Foo f2;
    };

    Foo StaticFoos::f1 = {1, &StaticFoos::f2};
    Foo StaticFoos::f2 = {2, &StaticFoos::f1};
 }

 static const &Foo f1 = StaticFoos::f1;
 static const &Foo f2 = StaticFoos::f2;
#else
 static Foo f1 = {1, &f2_};
 static Foo f2 = {1, &f1_};
#endif

Now in C and C++, you can access f1 and f2.

Eclipse
  • 44,851
  • 20
  • 112
  • 171
7

This seems to have a similar effect to Josh's answer, but with less complexity:

#ifdef __cplusplus
namespace {
    extern Foo f1;
    extern Foo f2;

    Foo f1 = {1, &f2};
    Foo f2 = {2, &f1};
}
#else
    static Foo f1;
    static Foo f2;

    Foo f1 = {1, &f2};
    Foo f2 = {2, &f1};
#endif

When compiled for C++, the extern definitions for f1 and f2 are exposed in the object file with an externally linkable symbol; however, because they are inside an anonymous namespace the symbols are mangled in such a way that they will not conflict with symbols from another translation unit.

With macro magic you could set things up so there's only one place where f1 and f2 are declared and defined, but if this is being generated mechanically there's probably not much reason to do that.

Something like:

#ifdef __cplusplus
#define START_PRIVATES namespace {
#define END_PRIVATES   }
#define PRIVATE extern
#else
#define START_PRIVATES 
#define END_PRIVATES   
#define PRIVATE static
#endif

START_PRIVATES
    PRIVATE Foo f1;
    PRIVATE Foo f2;

    Foo f1 = {1, &f2};
    Foo f2 = {2, &f1};
END_PRIVATES
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • yeah haha same thing i had in mind. you got my vote, thumbs up :) – Johannes Schaub - litb Mar 01 '09 at 23:54
  • additional, the extern stuff avoids using deprecated stuff (static being deprecated in C++) and allows them to be passed as template arguments (can't pass static variables to templates as parameters. only those having extern linkage). but they're still mangled effectively TU local. neat! :) – Johannes Schaub - litb Mar 01 '09 at 23:55
4

What you are trying to avoid is termed as Static Initialization Order Fiasco. You will be well served to use functions instead and also to initialize the individual objects with default values and then reseat the member pointers.

Your code samples mean radically different things. You will have to relook. The first one succeeds because you have a definition of one object and a declaration of another. This will work with both C and C++.

extern Foo f1;

This is a declaration and a tentative definition.

static Foo f1;

This is the declaration and definition of an object f1 of type struct Foo.

static Foo f2;

Ditto.

static Foo f1 = {1, &f2};

This is a redefinition. You have violated the One Definition Rule which says, there must be one and exactly one definition of a symbol in a translation unit. Outside of that you are allowed to have multiple definitions but of course each such occurrence has to have the same syntax and semantics.

static Foo f2 = {2, &f1};

Ditto.

extern Foo fn;
/* some code */
extern Foo fn;
/* some more ... */
Foo fn; /* finally a definition */

This is fine since it's ok to have multiple tentative declarations.

dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • Yes, that was stated that in the original post. The question is how to use static struct instances in C++ to achieve the same effect as C. It does not seem to not be possible in C++. It appears to be a language oversight. –  Feb 28 '09 at 18:14
  • @dirkgently: it is valid C because the C standard says: "Within one translation unit, each declaration of an identifier with internal linkage denotes the same object or function". – Michael Burr Feb 28 '09 at 18:35
  • It's surprising that this C functionality cannot be achieved in C++. Wasn't C++ designed to be a superset of C? –  Feb 28 '09 at 18:43
  • @Michael Burr:That is for external linkage, I believe. – dirkgently Feb 28 '09 at 19:01
  • @Michael Burr: Can't find the appropriate sections, so I'll go with you. My mistake. Thanks again. – dirkgently Feb 28 '09 at 19:06
  • That's a copy-n-paste from the C99 standard PDF (6.2.2). The same wording is in the C90 standard in 6.1.2.2. – Michael Burr Feb 28 '09 at 19:08
  • Annex C, 3.1/3 of c++98 explains what makes this not work in C++ (where tentative definitions are not allowed). have fun :) – Johannes Schaub - litb Mar 02 '09 at 00:04
  • I can't find any reference defining "static initialization order fiasco" that refers to multiple static definitions in one translation unit. As far as I can see, "static initialization order fiasco" refers to the familiar problem of the unspecified order in which global constructors are called among multiple translation units. – Kaz Jan 08 '21 at 22:00
2

You can't forward declare objects, only types. The extern solution is the correct solution. Or if you really need to avoid global namespace pollution, make them static and initialize them using a function which you call before all others.

EDIT: Michael Burr mentioned the reason in a comment, I figured I'd add it to a post:

@dirkgently: it is valid C because the C standard says: "Within one translation unit, each declaration of an identifier with internal linkage denotes the same object or function".

C++ has no such rule.

EDIT:

As noted in one other post. You can use an anonymous namespace as well to limit the scope of the variable. Just wrap the namespace stuff in an #ifdef __cplusplus and you should be good to go.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • It's a shame C++ lacks the ability to forward declare a struct instance as you can do in C. extern pollutes the global variable space, which is what I am trying to avoid. –  Feb 28 '09 at 18:18
1

I have encountered this problem. The restriction is frustrating and I don't see any reason why C++ has this gratuitous incompatibility with C.

My solution is to use static functions - which you can forward declare - which just return f1 and f2:

typedef struct Foo {
  int a;
  struct Foo* foo;
} Foo;

static Foo* link_f1();
static Foo* link_f2();

static Foo f1 = {1, link_f2()};
static Foo f2 = {2, link_f1()};

static Foo* link_f1() { return &f1; }
static Foo* link_f2() { return &f2; }

Unfortunately this isn't valid C so you'll still need different code for C and C++.

Edmund
  • 183
  • 5
  • The feature of C is called *tentative definition*, not forward declaration. `static Foo f1;` actually defines `f1`, but C lets you specify an initializer later on, so long as there is only one definition with initializer. This can also be done with arrays. – M.M Aug 08 '15 at 05:41
0

Here is what I did in my project. Instead of trying to solve this with an anonymous namespace, I used a named namespace.

[ And then thanks to Matt McNabb's helpful comments, it turns out that an anonymous namespace will do for a super tidy solution with fewer macros that generates no external name pollution. ]

This allows me to have two separate regions of program text, with regular file scope in between, for a tidy solution:

Everything is hidden behind these macros:

#ifdef __cplusplus
#define static_forward(decl) namespace { extern decl; }
#define static_def(def) namespace { def; }
#else
#define static_forward(decl) static decl;
#define static_def(def) static def;
#endif

And so we can do:

static_forward(struct foo foo_instance)

void some_function(void)
{
  do_something_with(&foo_instance);
}

static_def(struct foo foo_instance = { 1, 2, 3 })

The C expansion is straightforward, looking like this:

static struct foo foo_instance;

void some_function(void)
{
  do_something_with(&foo_instance);
}

static struct foo foo_instance = { 1, 2, 3 };

The C++ expansion looks like this:

namespace { extern struct foo foo_instance; }

void some_function(void)
{
  do_something_with(&foo_instance);
}

namespace { struct foo foo_instance = { 1, 2, 3 }; }

So, in a nutshell, thanks to anonymous namespaces, C++ does not actually have a static forward reference problem, only the problem of implementing it in a C incompatible way, which is bridgeable with macros.

Multiple anonymous namespace regions in the same translation unit are the same namespace, and the surrounding file scope outside of the namespace has visibility into it.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • You could use an anonymous namespace, `namespace {` . – M.M Aug 08 '15 at 05:34
  • @MattNcNabb I seem to believe, perhaps mistakenly, that each instance of an anonymous namespace is like a new namespace with a unique, secret name. This trick depends on two discontinuous namespace regions being the same namespace, since one declares a name and the other defines it. Moreover, outside regions refer to these identifiers. Is there a way to refer to a name inside a `namespace {` from the outside? – Kaz Aug 08 '15 at 05:36
  • Oh, I have misunderstood what your are doing. Your code in the "C++ expansion" causes undefined behaviour by having multiple definitions of `foo_instance`. My compiler (g++ 4.9.2) even rejects the code. – M.M Aug 08 '15 at 05:40
  • All anonymous namespaces in the same TU refer to the same namespace, so if you changed your code to `namespace { extern foo foo_instance; }` ... `namespace { foo foo_instance = {1,2,3}}` then the code is correct and it does not pollute the global namespace. – M.M Aug 08 '15 at 05:46
  • @MattMcNabb I wrote the expansion by hand and it is mistaken; the `extern` is missing. I will fix it. I'm using 4.6.3, and all is fine. The `extern` does appear in the macros (they are a cut and paste of the real thing). – Kaz Aug 08 '15 at 05:59
  • 1
    @MattMcNabb Alas, even though the namespaces refer to the same one, how do we refer into the namespace from the outside? AH, I see, there is an implicit "using namespace ". – Kaz Aug 08 '15 at 06:02
0

I would create two files (.cpp and .h):

code.h:

typedef struct Foo {
  Foo() {}
  Foo(int aa, struct Foo* ff) : a(aa), foo(ff) {}
  int a;
  struct Foo* foo;
} Foo;

static Foo f1;
static Foo f2;

code.cpp:

void myfun()
    {
    f1 = Foo(1, &f2);
    f2 = Foo(2, &f1);
    }

I would also prefer to put all variables like f1, f2 ... into some kind of "storage" object (of my own class or e.g. some STL container). Then, I would define this object as a static one.

Wacek
  • 4,326
  • 2
  • 19
  • 28