tags:

views:

412

answers:

7

I'm writing C++ using the MinGW GNU compiler and the problem occurs when I try to use an externally defined integer variable as a case in a switch statement. I get the following compiler error: "case label does not reduce to an integer constant".

Because I've defined the integer variable as extern I believe that it should compile, does anyone know what the problem may be?

Below is an example:

test.cpp

#include <iostream>
#include "x_def.h"

int main()
{
   std::cout << "Main Entered" << std::endl;


   switch(0)
   {
      case test_int:
         std::cout << "Case X" << std::endl;
         break;
      default:
         std::cout << "Case Default" << std::endl;
         break;
   }

   return 0;
}

x_def.h

extern const int test_int;

x_def.cpp

const int test_int = 0;

This code will compile correctly on Visual C++ 2008. Furthermore a Montanan friend of mine checked the ISO C++ standard and it appears that any const-integer expression should work. Is this possibly a compiler bug or have I missed something obvious?

Here's my compiler version information:

Reading specs from C:/MinGW/bin/../lib/gcc/mingw32/3.4.5/specs
Configured with: ../gcc-3.4.5-20060117-3/configure --with-gcc --with-gnu-ld --with-gnu-as --host=mingw32 --target=mingw32 --prefix=/mingw --enable-threads --disable-nls --enable-languages=c,c++,f77,ada,objc,java --disable-win32-registry --disable-shared --enable-sjlj-exceptions --enable-libgcj --disable-java-awt --without-x --enable-java-gc=boehm --disable-libgcj-debug --enable-interpreter --enable-hash-synchronization --enable-libstdcxx-debug
Thread model: win32
gcc version 3.4.5 (mingw-vista special r3)

+4  A: 

Case labels have to be compile-time constants. That means the compiler must be able to substitute the value at compile-time. Although your values are constant, the compiler can't know their values until at least link-time.

Fred Larson
+3  A: 

VC++ is right, and g++ is wrong. A case label is required to be an integral constant expression (§6.4.2/2) and a const variable of integer type initialized with a constant expression is a constant expression (§5.19/1).

Edit:mostly for Pavel, and his suggestion of a possible DR. §5.19/2 has been completely rewritten already. C++0x adds a whole new concept of a constexpr that expands what's considered a constant expression considerably. For example, under the current standard, something like:

int x() { return 10; }
const int y = x();

y is not a constant expression. We can all easily see that it's (indirectly) initialized with a literal 10, but the compiler still can't allow it as a constant expression. Under the new standard, it'll be possible designate x() as a constexpr, and y will be a constant expression.

As it's formulated in N2960, §5.19/2 says an expression is a constant expression unless it uses something from the following list. It then gives about a page-long list, but using a const variable that isn't initialized in the current compilation unit doesn't seem to be one of them. [Edit: see below -- reading CWG Issue 721, I've changed my mind.]

As far as VC++ being right and g++ wrong, I meant only in this very specific respect. There's no question that both are "wrong" if you're talking about getting every part of the standard correct. I doubt anybody's even working on implementing export for either one.

export does, however, point out a degree to which C++ seems willing to postpone decisions until link time. Two-phase name lookup means that when an exported template is compiled, there's a lot more than just constant expressions that it doesn't know for sure. It might not even know whether a particular name refers to a function or an object -- but there's no question that the standard does require exactly that. The issue at hand strikes me as a substantially simpler one to deal with.

Edit: I did a bit of searching, and found Core Working Group Issue 721. Jame's question parallels the one at hand quite closely ("However, this does not require, as it presumably should, that the initialization occur in the same translation unit and precede the constant expression..."). The proposed resolution adds the phrase: "...with a preceding initialization...". At least as I read it, that means that the committee agreed that under the current standard, the code must be accepted, but under the new standard it's not allowed.

That wording was agreed upon in July of this year, but doesn't (yet?) appear in N2960, which I believe is the most recent draft of C++0x.

Jerry Coffin
I took 5.19/1 to mean initialized with a constant expression *in this translation unit*. OK, in some other translation unit which hasn't been written yet, maybe it's initialized with a constant expression. Or with a non-constant expression. The compiler has no way of knowing. I honestly don't think the intention of the standard is to defer until link time the generation of switch statements (and all other code that uses integral constant expressions, for instance array sizes and the sizes of bit fields).
Steve Jessop
Or looking at it another way: in the context of compiling test.cpp, test_int is not initialized with a constant expression. It's not initialized at all, it's only declared extern. I don't think `sizeof` could work, if it can't know the size of an array member of a struct until some dll is loaded that contains the initialization of an `extern const int`.
Steve Jessop
@Steve, there's nothing in the text of the Standard to support this interpretation. I, too, think that's what was likely intended, but that's not what it currently says. Perhaps it's time for a DR.
Pavel Minaev
@Steve:while your position makes sense, I can't find anything in the standard to support it.
Jerry Coffin
I agree, the standard should be clearer. Perhaps my "in this translation unit" is wrong, maybe it should be even stronger: "before the point of use". I think the language is ambiguous, and that it can be read my way. Charles is making the same points better than me, though.
Steve Jessop
... plus the standard requires that for the switch statement to be correct, no two `case` constants can have the same value. This means that the compiler must be able to identify the values in the constants when compiling the switch.
David Rodríguez - dribeas
I see what you mean about templates and export. Considering the C++ standard as the work of human beings, we know that export was an ill-considered late addition, that it requires several things the authors didn't realise it required, and therefore that it probably shouldn't be taken as an indicator of the intent of the rest of the standard. But considering the C++ standard as an object dropped out of the firmament to enrich our lives, we'd have to at least entertain the possibility that the wording is not merely ambiguous and in need of a TC, but actually does mean what we fear.
Steve Jessop
@Steve:I think at least some of the committee (e.g., John Spicer) did have a pretty good idea of what `export` would entail. IIRC, John Spicer spent considerable effort campaigning against it, but eventually conceded the fight -- and then spent the next five years or so creating the only working implementation to date...
Jerry Coffin
OK, perhaps the others just had unrealistic expectations of how much time C++ compiler writers would have on their hands. Anyway, I disbelieve that they intended switch statements and arrays to be just as difficult to implement as exported templates ;-)
Steve Jessop
@Steve:as I said before, I don't think this is *nearly* as difficult as exported templates -- it's finding a single value in a (fairly) simple table, not even in the same league as two-phase name lookup. To put things in perspective, even the most skeptical immediately know exactly what this requires. The rules for template name lookup take up 10 pages of the standard, and those pages are so dense not 1 C++ programmer in 1000 knows what they really say.
Jerry Coffin
So how about `void foo(int ( extern const int test_int; int ra[test_int]; foo(ra);`? The linker has to check parameter types. And `template <typename T> class Foo {}; template <typename T> void foo(T } foo(ra) // as before`. The linker is instantiating template classes, and I haven't even used export.
Steve Jessop
The point isn't the complexity of the standard, it's how much of it is deferred from compile time to link time. I've a strong suspicion that just this one change means "most of it". As soon as arrays have sizes not determined until link time, that means you have types not completed until link time, because C++ does not rejoice in VLAs. Template-wise, it's game over.
Steve Jessop
@Steve:At this point it seems like you're putting a lot of energy into proving this needs to be changed -- even though the committee has already decide to make the change. If you think they change they're making is inadequate, I'd love to hear what you'd advocate instead, but more proving that it needs to change doesn't seem (at least to me) to accomplish much when it's already been decided that it will change. At the same time, proving that it's bad or hard to implement doesn't change the requirement of the current wording.
Jerry Coffin
I'm not proving it needs to change, as you say there's no argument there. I'm explaining why, of the two possible interpretations of this wording, compiler writers have had to choose the sane one, and not the one that you say is a fairly simple table lookup, and I say is next to impossible to implement. Everyone seems to agree what the change to the wording should be in order to avoid misunderstanding (or, if you can't agree that it's currently vague, the change needed in order to convey the only plausible meaning that could ever have been intended).
Steve Jessop
If you look at the description of issue 721 it doesn't apply directly to the wording in the current standard but at a draft of the new standard (with `constexpr` constant expressions are having something of an overhaul). Even then, I don't think it's obvious from the notes that the defect was definitely that the previous wording explicitly allowed something that wasn't intended rather than just being ambiguous.
Charles Bailey
Well, the reason I think it's ambiguous is that the lost-looking participle "intialized" in English might mean "which has been initialized", or might mean "which is initialized". The intended meaning is the former, and I don't think the wording forbids the intended reading. But the correction is needed because the wording fails to prevent the latter reading (even if, as Charles argues elsewhere, that latter reading contradicts the defined compilation phases). This is what happens when you invent a language far too complicated to be formally defined...
Steve Jessop
@Steve:But the standard leaves no room for argument over what "initialized" means, and it doesn't depend on the normal English definition either -- §8.5 of the standard defines that "initialized" means within the standard (and it doesn't support your view that "initialized" can be read as requiring that the compiler has already "seen" the initializer for an object to be initialized).
Jerry Coffin
Of course we may be wasting our time here - it really doesn't matter whether it's possible to give the standard the benefit of the doubt as using English ambiguously, or just say that we think it's flat-out incorrect (and self-contradictory, if Charles' analysis of the compilation phases is sound). It's just about what kind of mistake we think the authors made - I think they used an English word in one of its actual English meanings. You think they used a jargon term inaccurately. Either way, they didn't express themselves as they should.
Steve Jessop
The pseudo-BNF in §8.5/1 defines initialization in general, then the rest goes on the give more detail for specific circumstances. Since none of the others says that in this case it's not initialized, and it fits the grammar given in §8.5/1, it's initialized.
Jerry Coffin
Doesn't follow - you can't derive a jargon meaning of "initialized" from a jargon meaning of "initialization" just by picking one of several possible English correspondences between the two, and applying it (and a perverse choice at that, given that we know which one the authors intended and it is not this). I don't see why this concept is so difficult to get across, or do you also disagree that it's potentially ambiguous in general English?
Steve Jessop
Specifically, the only appearance of the word "initialized" in 8.5/1 is "the identifier designates an object or reference being initialized". Not "the object or reference designated by the identifier is said to be *initialized* ", which might be seen as an attempt to define "initialized". The index contains no mention of "initialized", which to me is strong evidence that it is not intended to be a defined jargon term in the spec. Rather, we're expected to use our knowledge of English to figure out what they mean.
Steve Jessop
Thanks everyone for taking the time to look into the issue. I can't believe that there were over 50 responses within 24 hours!If I understand your responses correctly it sounds like either there is a problem with g++ or the standard itself is ambigous.Either way, I have to use g++ so to solve my origional problem I'm going to have to restructure my code so that the variable being used in the switch statement won't need to be initialized in a seperate compilation unit.Thanks again!
C Nielsen
+4  A: 

A case label requires an integral constant expression which have strict requirements that enable their value to be determined at compile time at the point of use.

From 5.19 [expr.const], "an integral constant expression can involve only literals (2.13), enumerators, const variables or static data members of integral or enumeration types initialized with constant expressions (8.5),...".

At the point at which you use test_int where a constant expression is required, it is a const variable declared extern and without any initializer and does not meet the requirements for a constant expression, despite the fact that you do actually initialize it with a integral constant expression in another translation unit. (*This is not completely clear from the wording of the standard but is my current interpretation of it.)

The restrictions in the standard disallow usages such as:

void f(int a, int b)
{
    const int c = b;

    switch (a)
    {
    case c:
        //...
    }
}

In your example, when the compiler is compiling test.cpp, it has no way to determine what the initializer might be in x_def.cpp. You might have done:

const int test_int = (int)time();

Clearly, in neither of these examples could the value of the const int be determined at compile time which is the intention for integral constant expressions.

Charles Bailey
I dare say it's still a defect in the Standard. The text you reference doesn't say anything about "same translation unit". It says "const variables" - check - "initialized with constant expressions" - check.
Pavel Minaev
I agree that it's not as clear as it could be, but I would still defend my interpretation. If we forget the other translation unit and look at only test.cpp: `extern const int test_int;` is test_int a const variable initialized with a constant expression? I would say the implementation is entitled to say no at this point and reject the translation. Section 2.1 says that translation units are semantically analyzed before translation units are combined and the constant expression appears to be a semantic restriction that should be applied in step 7 of the phases of translation.
Charles Bailey
@Charles:Your example is quite a bit different. The standard is clear that the requirement is not only for a `const` variable, but that the `const` variable be initialized with a constant expression -- and your parameter `int b` clearly is *not* a constant expression, so `c` clearly isn't a constant expression.
Jerry Coffin
@Jerry Coffin: Yes, my examples were supposed to show other things that weren't allowed, I wasn't 100% sure that the original question was clear about the difference between a `const int` and a integral constant expression from the use of "const-integer expression". I still think that an `extern const int` with no initializer doesn't meet the requirements even if the variable is (elsewhere) initialized with a suitable expression, but fully concede that this is an interpretation.
Charles Bailey
@Pavel Minaev:in case you haven't read my updated answer, the committee has already agreed that it's a defect, and voted to add wording about "with a preceding initialization" to the next version of the standard.
Jerry Coffin
Thanks, Jerry. That clears it up.
Pavel Minaev
A: 

Here's a simpler test:

test_int.cpp:

const int test_int = 10;

main.cpp:

#include <iostream>
using std::cout;
using std::endl;

extern const int test_int;

int main() {
    cout << test_int << endl;
    return 0;
}

In G++, I get an undefined reference. However, doing the same thing in C works. According to http://gcc.gnu.org/ml/gcc/2005-06/msg00325.html , a const variable implicitly has internal linkage in C++. This doesn't appear to be the case in C.

Joey Adams
You can still make a `const` variable with external linkage in C++ if you declare it as `extern const` before defining.
Pavel Minaev
+1  A: 

MS compiler is being a bit naughty here. When you compile the the constant initialization and the case statement using the constant in the same compilation unit it works out the constant value at compile time.

Once you attempt to use the extern const outside of the compilation unit where it's initialised (i.e. the cpp file containing initialization or any of the files it includes) the compiler will barf with pretty much the same error. Fred Larson is correct the compiler shouldn't know the constant value until link time and thus it must not be acceptable as a switch constant, it's just MS compiler cheats a little bit.

The solution to your problem would be to use macros, is there any reason why you don't want to #define the constant?

Igor Zevaka
You don't even have to #define it. A `const int` with internal linkage, initialized with a constant expression, would be fine. This can be done in a header.
Steve Jessop
Indeed that does work. It's almost like it should be part of enumeration since it gets used inside a switch statement. Hard to tell without knowing the purpose of the program.
Igor Zevaka
Where do you see the initialization and case statement in the same compilation unit in the demo code in the question?
Pavel Minaev
In that case the OP code does not compile in MSVC 2008 with default settings. I get `error C2051: case expression not constant`.
Igor Zevaka
+1  A: 

I cannot reproduce this on a trivial example using VC++2008:

test.cpp:

extern const int n;
int main() {
    switch (0) {
    case n: break;
    }
}

test2.cpp:

extern const int n = 123;

compile with:

cl.exe test.cpp test2.cpp

output:

test.cpp(4) : error C2051: case expression not constant

Pavel Minaev
modules have to be individually compile (not link) time visible. try including test2.cpp in test.cpp like the OP and it should work.
Martin Beckett
I don't see an `#include` of one .cpp into another in his example. He's #including the header in both .cpp's, but the header only has the `extern const` declaration, not the definition with initializer.
Pavel Minaev
A: 

I'm using a "gcc (SUSE Linux) 4.3.2" and having a similar effect, that still is a bit stranger.

My definitions are:

namespace operations{
   const cOpDummy OpDummy();
   const cInitOperator InitOperator();
};

const unsigned long ulNumberOfOperations = 2;

const cOperation * arrayOperations[] = {
   & (operations::OpDummy),
   & (operations::InitOperator)
};

And the extern declarations in an other file are:

extern const unsigned long ulNumberOfOperations;
extern const cOperation * arrayOperations[];

The funny thing is: The compiler gives just for "ulNumberOfOperations" "undefined reference to ulNumberOfOperations", but is Ok with "arrayOperations[]". My workaround is to declare "ulNumberOfOperations" not constant.

BioKom