tags:

views:

304

answers:

5

I have two static member declarations in ClsA, like this:

class ClsA {
public:
   static unsigned short m_var1;
   static unsigned short m_var2;
};

unsigned short ClsA::m_var1 = 1001;
unsigned short ClsA::m_var2 = 1002;

In ClsB, I use those static member declarations from ClsA like this:

unsigned short var1; // assume var1 is declare/use some where in the code.

switch( var1 ) {
case ClsA::m_var1:  // Error: cannot appear in a constant-expression
   break;

case ClsB::m_var2:  // Error: cannot appear in a constant-expression
   break;
}

Why do I get an error if I use that in a switch statement? There is no error if I use it in an if statement.

+10  A: 

Try

static const unsigned short m_var1;

Stefan
And just in case you'd like to understand the difference: `switch` needs compile-time constants. `if` is happy with run-time values.
Christopher Creutzig
Some people prefer to abuse enumerates (me included). Partly history, as these `const` constructs weren't always considered compile-time constant. It's also possible to get around the `const` and modify `m_var1`, e.g using a `const_cast`. This will result in undefined behaviour - the switch may or may not notice the new value, for instance, depending on how the compiler chose to implement it. The compiler can use different implementations for different cases, so you *could* get a bug appear because you added/removed a `case` for instance. Probably not easy to find the cause.
Steve314
@Steve: Modifying physically `const` objects is already UB - doing *that* is a bug.
Georg Fritzsche
@Georg: are you sure? Comeau accepts it either with the constant initialized in the declaration, or with the constant declared in the class (but not initialized in the declaration) and then initialized in a separate definition, as you'd do for a non-const static member.
Steve Jessop
@Steve: Interesting, VC and GCC accept it too if the definition is visible. But i'm sure as the standard is quite clear (*"In that case [...]"*) and there is no *"If the initializer is visible ..."* clause or something similar.
Georg Fritzsche
@Georg - you and I both know it's impossible to write a serious C or C++ app. without ever using anything that the language standards leave undefined. Even the meaning of "short" isn't fully defined (how short is short?). Obviously the whole point of const is that it shouldn't be modified, but there are exceptions - e.g. working with someone elses code where someone was over-eager with the "const" but you're no allowed to change it. If "const_cast" was never to be used, why define it in the first place?
Steve314
@Georg - and as for changing the const being a bug - yes, that's kind of my point. As I said, changing the case clauses can make the bug "appear" (ie visible), and "probably not easy to find the cause" because the bug isn't the switch statement you just modified, it's the constant being mutated god knows where.
Steve314
@Georg - the point I'm making here is that something being "undefined behaviour" doesn't mean you don't have to consider cases where it might, accidentally or deliberately, have been done. It is reasonable to discuss the pros and cons of programming practices designed to mitigate these things, and not helpful for people to just kneejerk-reject the whole idea with "that's undefined - there was a bug already so this is nonsense", ignoring the fact that avoiding susceptibility to bugs the compiler won't detect is the whole point.
Steve314
@Georg, Steve - as for what a particular compiler or set of compilers accepts, is this really a big surprise? Every compiler accepts and defines things that the standards leave undefined. This isn't a surprise - it's just the facts of life with C and C++.
Steve314
@Steve: I don't want to start a multi-page discussion in comments, but `const_cast` is for when you have a `const` reference or pointer to a non-`const` object, not to allow modification of something physically `const`. If you only want to use language features where no-one can make any mistakes you have to look for another language.
Georg Fritzsche
@Georg - a static variable may be referenced by a pointer or reference. Errors are quite often several steps removed from their visible symptoms. And as for your "no-one can make any mistakes", all I did was try to discuss a way of mitigating the possibility of mistakes. I didn't realise that was against the rules. In fact, I thought taking reasonable steps to ensure that mistakes are likely to caught by the compiler was even considered a part of good programming practice. Of course what is "reasonable" is subjective, but again, that's the point of discussing the pros and cons of an approach.
Steve314
@Georg - WRT multi-page discussions in comments - you rejected my point in comments and I defended my point in comments. Defending it anywhere else makes no sense, so disallowing that effectively means I'm not allowed to defend myself. I'm probably going on a bit much, but that's aspergers for you - I know I tend to be over-formal, pedantic etc so I put effort into trying to be concise, but I also know I tend to get misunderstood and the goal of being clear and specific is obviously in conflict. And my first comment in this answer *was* apparently misunderstood.
Steve314
@Georg: I think the natural inference from "in that case" (namely that it's *only* in that case) contradicts 5.19/1, which says that ICEs can involve "static data members of integral or enumeration types initialized with constant expression". The defn. of ICE doesn't seem to care *where* it's initialized (in the class or outside). So I think the compilers are right, and the language in 9.4.2/4 is slightly misleading, since there are also other cases where the value can be used in an ICE.
Steve Jessop
@Steve314: I would be somewhat surprised if Comeau were wrong on this - it's a *very* pedantic compiler. Unless the actual meaning of the standard were in dispute, the people at Comeau would consider it a needs-fixing bug to fail to issue a diagnostic where it should issue one. Using something in a constant expression that shouldn't be there isn't stated to be UB in the standard: it's a breach of language rules, which is a different thing. So compilers should diagnose, and Comeau is deadly serious about such responsibilities...
Steve Jessop
@Steve - OK, looks like I was wrong on that one.
Steve314
@SteveJ: Hm, but why should be adding *"in that case"* specifically be neccessary if it doesn't mean *"only"*? I didn't find anything enlightening in the DRs, so i guess i'll just post a question about it after getting sleep :)
Georg Fritzsche
@Steve314: After re-reading my comment i can't see where you got your interpretations. I simply find it hard to follow longer discussions and the SO format doesn't support them well. I'd like to strike that last sentence though - what i meant to say was you can't protect against everything anyway (e.g. 0-pointer dereference) and for me intentionally modifying const values is beyond the line. I wouldn't choose enums over static const integral for that very reason, they convey other intentions.
Georg Fritzsche
@Georg: Like I say, it's a bit confusing, but I might say "if you ask nicely, I'll give you a cookie". Also, if you threaten to stab me, I might give you a cookie, unless I'm sure you're bluffing. I happen not to have mentioned your second option, but its existence doesn't make my original statement untrue. In the case where a static const int member is initialized in the class, it necessarily is initialized with a constant expression and hence can be used in an ICE. A static const int member initialized elsewhere *might* be initialized with a CE, or then again might not.
Steve Jessop
@Georg - on "intentionally modifying const", you may be surprised, but I agree with you. I'm not some const-mutating anarchist, and I'm not surrounded by them either. As I said from the start - "Partly history, as these const constructs weren't always considered compile-time constant" - IOW old habit from when the only choice was between `enum` and `#define`. Defensive programming is the sole remaining reason for it, and on reflection it probably *is* just an inertia excuse.
Steve314
@SteveJ: Its more that it mentions inline initialization (and only that) explicitly in the first place if it is already covered by 5.19/1. But i clearly have a misconception there, thanks for clearing it up.
Georg Fritzsche
A: 

m_var1 and m_var2 aren't constants. But the cases in switch have to be constant expressions (1, 4*8, some_const+1).

delnan
A: 

It's because the expressions after the case keyword must be a compile time constants and your m_var1 and m_var2 arent. If need to do this kind of test, use an if chain.

http://gcc.gnu.org/ml/gcc-help/2005-12/msg00069.html talks about this error.

cube
+10  A: 

C++ requires the case to have a constant-expression as its argument. What does that mean? It means that the only operands that are legal in constant expressions are:

  • Literals
  • Enumeration constants
  • Values declared as const that are initialized with constant expressions
  • sizeof expressions

In your case, if you declared your static members as const, and initialized them when declared with an integral constant expression, you could use them in switch-case statements. For example,

class ClsA {
    public:
        static const unsigned short m_var1 = 13;
        static const unsigned short m_var2 = 42;
};

If, on the other hand, you insist on switching on a variable to avoid multiple if-else if statements, I would suggest using a jump table (it's also referred as a lookup table).

Michael Foukarakis
A: 

The values must be compile time constants as the error message indicates. They should be declared and defined as const inside the class declaration, such that the compiler knows about them and their values at any point of compilation, typically in a header file.

class ClsA {
public:
   static unsigned short const m_var1 = 1001;
   static unsigned short const m_var2 = 1002;
};

In some object file you should then also instantiate these const variables

unsigned short const ClsA::m_var1;
unsigned short const ClsA::m_var2;

that is without repeating the initialization value and without the static keyword.

Jens Gustedt
I don't think the second part is necessary for constants initialized inline.
visitor
For C++ they are often necessary if you pass such constants by reference to a function. (There might be other extreme cases of usage that require this, too). Since in such case you will only know of a missing instantiation once you (or somebody else using your library) try to link. This might be difficult to track down, so I'd suggest that you always instantiate such kind of `const` variables.
Jens Gustedt