Having static const variables with GCC 3.3 similar to GCC 3.4

345 Views Asked by At

When using g++ 3.4.6 (with ld 2.15.92.0.2), I can write:

class ConstantContainer {
public:
    static const uint16_t MyConstant1 = UINT16_C(0x4321);
    static const uint32_t MyConstant2 = UINT32_C(0x87654321);
    static const uint64_t MyConstant3 = UINT64_C(0xF0E1D2C3B4A59687);
    static const double MyConstant4 = 1.0 / 4.0;
};

and use ConstantContainer::MyConstant1 and others almost everywhere as a cheep substitute for scoped literals, with the exception of initializing other constants.

However, when using g++ 3.3.6 (with ld of the same version 2.15.92.0.2, although not the same binary and distro), the code compiles fine, too, but linking fails in some cases due to unresolved reference at any point a “constant” is used:

g++ -o myapp module1.o module2.o ... main.o
moduleN.o(.text+0x59e): In function `BlahBlah(const FooBar&)':
: undefined reference to `ConstantContainer::MyConstant1'

I could not figure out that are the unique features that provoke such a behavior. For example, a non-compatible case may be as simple as this:

class GraphConversionState {
public:
    struct NodeIndex {
    public:

        typedef CxxStd::uint32_t ValueType;
        ValueType Value;

        class ValueSpecial {
        public:
            static CxxConstExpr ValueType
                Unknown = UINT32_C(0xFF000000),
                Isolated = UINT32_C(0xFF111111),
                Connected = UINT32_C(0xFFCCCCCC);
        };
    };
};

I. e. there is only a small bunch of static constant members of uint type, yet they don't qualify to be treated as named literals; meanwhile, in other cases, even floating point values are fine. The only obvious difference is scope level (class nesting), but that doesn't prove to be the real reason in general case with simplified examples.

The obvious workaround is to turn the aforementioned class into a monster:

class ConstantContainerType {
public:
    uint16_t MyConstant1;
    uint32_t MyConstant2;
    uint64_t MyConstant3;
    double MyConstant4;
    ConstantContainerType() :
        MyConstant1(UINT16_C(0x4321)),
        MyConstant2(UINT32_C(0x87654321))
        MyConstant3(UINT64_C(0xF0E1D2C3B4A59687))
        MyConstant4(1.0 / 4.0)
        { }
};
static const ConstantContainerType ConstantContainer;

// in ConstantContainer.cpp:
const ConstantContainerType ConstantContainer;

But that is quite ugly, less clean and much more error-prone, as number of constants and container classes is high. Even more so, while in-place declared and defined constants are probably optimized as they were real literals, it is very doubtful they would be treated so when being a part of a singleton object.

So I wondered: what are the exact rules employed by GCC 3.3 and above for treating some static const POD declarations as constant definitions?

3

There are 3 best solutions below

4
user3528438 On

Either you can define it elsewhere,

class ConstantContainer {
public:
    static const uint16_t MyConstant1;
    static const uint32_t MyConstant2;
};
ConstantContainer::MyConstant1 = UINT16_C(0x4321);
ConstantContainer::MyConstant2 = UINT32_C(0x87654321);

or make the second cleaner by declaring the members as constants.

class ConstantContainer {
public:
    const uint16_t MyConstant1;
    const uint32_t MyConstant2;
    ConstantContainer(uint16_t foo, uint16_t bar) 
        :MyConstant1(foo), MyConstant2(bar)
    {}
};
2
MSalters On

The old trick still works:

class ConstantContainer {
public:
    enum { MyConstant1 = UINT16_C(0x4321) };
    enum { MyConstant2 = UINT32_C(0x87654321) };
};

Of course, if you want actual objects of type uint16_t/uint32_t, they'll have to live somewhere.

2
Ulrich Eckhardt On

Just use a namespace instead of abusing classes like that:

namespace ConstantContainer {
    uint16_t const MyConstant1 = UINT16_C(0x4321);
    uint32_t const MyConstant2 = UINT32_C(0x87654321);
}

In addition, constants at namespace scope have internal linkage by default (like static objects at namespace level) so you can declare and define them in a header file without risking ODR violations.