tags:

views:

956

answers:

8

I'd like to define a constant char* in my header file for my .cpp file to use. So I've tried this:

private:
    static const char *SOMETHING = "sommething";

Which brings me with the following compiler error:

error C2864: 'SomeClass::SOMETHING' : only static const integral data members can be initialized within a class

I'm new to C++. What is going on here? Why is this illegal? And how can you do it alternatively?

+13  A: 

You need to define static variables in a translation unit, unless they are of integral types.

In your header:

private:
    static const char *SOMETHING;
    static const int MyInt = 8; // would be ok

In the .cpp file:

const char *YourClass::SOMETHING = "sommething";

C++ standard, 9.4.2/4:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression. In that case, the member can appear in integral constant expressions within its scope. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

Samuel_xL
so it's impossible for me to assign this in the header file? I'm able to do this with other datatypes, like DWORD. Why is this?
Mark
Because a pointer is not an integral type. Integral only comprises the built-ins.
Matthieu M.
Why is this the case? What limitation has caused this restriction to be limited in C++?
Mark
Specifically, DWORD is an integer type. Types which are not integer types include (for example) pointers and floating-point types.
Steve Jessop
Because the C++ standard says so. I'll edit my answer
Samuel_xL
`DWORD` is one of the many typedef's defined in the Windows SDK. It translates to `unsigned int`, which is an integral type.
Steve Guidi
+8  A: 

The error is that you cannot initialize a static const char* within the class. You can only initialize integer variables there.

You need to declare the member variable in the class, and then initialize it outside the class:

// header file

class Foo {
    static const char *SOMETHING;
    // rest of class
};

// cpp file

static const char *Foo::SOMETHING = "sommething";

If this seems annoying, think of it as being because the initialization can only appear in one translation unit. If it was in the class definition, that would usually be included by multiple files. Constant integers are a special case (which means the error message perhaps isn't as clear as it might be), and compilers can effectively replace uses of the variable with the integer value.

In contrast, a char* variable points to an actual object in memory, which is required to really exist, and it's the definition (including initialization) which makes the object exist. The "one definition rule" means you therefore don't want to put it in a header, because then all translation units including that header would contain the definition. They could not be linked together, even though the string contains the same characters in both, because under current C++ rules you've defined two different objects with the same name, and that's not legal. The fact that they happen to have the same characters in them doesn't make it legal.

Steve Jessop
+1  A: 

Constant initializer allowed by C++ Standard only for integral or enumeration types. See 9.4.2/4 for details:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a name- space scope if it is used in the program and the namespace scope definition shall not contain an initializer.

And 9.4.2/7:

Static data members are initialized and destroyed exactly like non-local objects (3.6.2, 3.6.3).

So you should write somewhere in cpp file:

const char* SomeClass::SOMETHING = "sommething";
Kirill V. Lyadvinsky
+2  A: 

If you're using Visual C++, you can non-portably do this using hints to the linker...

// In foo.h...

class Foo
{
public:
   static const char *Bar;
};

// Still in foo.h; doesn't need to be in a .cpp file...

__declspec(selectany)
const char *Foo::Bar = "Blah";

__declspec(selectany) means that even though Foo::Bar will get declared in multiple object files, the linker will only pick up one.

Keep in mind this will only work with the Microsoft toolchain. Don't expect this to be portable.

asveikau
A: 

There is a trick you can use with templates to provide H file only constants.

(note, this is ugly...)

(values.h file)

template<int dummy>
class tValues
{
public:
   static const char* myValue;
};

template <> const char* tValues<0>::myValue = "This is a value";

typedef tValues<0> Values;

(main.cpp)

#include "values.h"

int main(blah...)
{
   printf("%s", Values::myValue);
}

In MSVC, you might be able to use the __declspec(selectany)

For example:

__declspec(selectany) const char* data = "My data";
Torlack
The templated trick is incorrect. It is illegal to provide multiple definitions of an explicit specialization. This might work if the compiler fails to catch it, but this is a violation of ODR nevertheless.
AndreyT
Actually, the correct implementation of the template trick would use non-specialized declaration.
AndreyT
Just another case where the MS compiler doesn't follow all the standards? I have to admit I've never tried it on GCC.
Torlack
Torlack, violating the ODR leads to undefined behavior. "Works as intended" is one possible behavior. It's impossible *not* to follow the standards when it comes to UB.
Rob Kennedy
@AndreyT: I have problems taking your statement at face value. In the current standard in 14.7 [temp.spec]/5 it does state that it is an error specializing a template in a program more than once for the same parameter list, so I must agree on that. Now, my problem is with the fact that the code above does not seem to differ much from including vector in two compilation units and using the `std::vector<bool>` in both of them. The symbols will be compiled in both C.U. but that is not really a violation of ODR, there is a single definition, whether it happens to be in more than one compilation...
David Rodríguez - dribeas
... unit. How would that differ from having the code in this answer in a header and including it from different compilation units? There is only a single definition (as Torlack pointed, the definition of the static constant happens within the header file), just included many times. Am I missing something here?
David Rodríguez - dribeas
@dribeas: Yes, you are missing the actual definition of ODR, given in 3.2. More precisely, in 3.2/5 it lists everything that can be defined multiple times (as long as the definitions are in different translation units). Template specialization are included that list *as long as some template parameters are not specified*. Once all parameters are specified, template cannot be defined in multiple translation units. The rationale behind this is understandable: a fully specialized template is no longer a template.
AndreyT
... A fully specialized template becomes ordinary function or ordinary data member, and the ODR applies to them accordingly. Again, as you noted yourself, 14.7/5 also says that. Your point about `std::vector<bool>` is an interesting one though. I'd say that explicitly specialized *class* template becomes an ordinary class and ODR for classes applies to it. I.e. it supposed to be OK to have multiple definitions of a class in a program. However, I don't understand how it works with 14.7/5.
AndreyT
I'm strating to suspect that the problem with the code in this post (reported by GCC, for example), it really caused by the presence of the initializer, not by multiple explicit specialization. Without the initializer it will probably compile even in GCC (I can't try now).
AndreyT
+5  A: 

To answer the OP's question about why it is only allowed with integral types.

When an object is used as an lvalue (i.e. as something that has address in storage), it has to satisfy the "one definition rule" (ODR), i.e it has to be defined in one and only one translation unit. The compiler cannot and will not decide which translation unit to define that object in. This is your responsibility. By defining that object somewhere you are not just defining it, you are actually telling the compiler that you want to define it here, in this specific translation unit.

Meanwhile, in C++ language integral constants have special status. They can form integral constant expressions (ICEs). In ICEs integral constants are used as ordinary values, not as objects (i.e. it is not relevant whether such integral value has address in the storage or not). In fact, ICEs are evaluated at compile time. In order to facilitate such a use of integral constants their values have to be visible globally. And the constant itself don't really need an actual place in the storage. Because of this integral constants received special treatment: it was allowed to include their initializers in the header file, and the requirement to provide a definition was relaxed (first de facto, then de jure).

Other constant types has no such properties. Other constant types are virtually always used as lvalues (or at least can't participate in ICEs or anything similar to ICE), meaning that they require a definition. The rest follows.

AndreyT
A: 

To answer the why question, integral types are special in that they are not a reference to an allocated object but rather values that are duplicated and copied. It's just an implementation decision made when the language was defined, which was to handle values outside the object system and in as efficient and "inline" a fashion as possible.

This doesn't exactly explain why they are allowed as initializors in a type, but think of it as essentially a #define and then it will make sense as part of the type and not part of the object.

DigitalRoss
A: 
class A{
public:
   static const char* SOMETHING() { return "something"; }
};

I do it all the time - especially for expensive const default parameters.

class A{
   static
   const expensive_to_construct&
   default_expensive_to_construct(){
      static const expensive_to_construct xp2c(whatever is needed);
      return xp2c;
   }
};
pgast