5
votes

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)

8
Interesting: one pragmatic and two standard-based answers, all from people with >5K rep, and the standard-based answers disagree. Methinks the standard isn't as clear as it could be.David Thornley
@David Thronley: Such is the joy of standards :) .CB Bailey
I'm stuck trying to determine the relevance of your friend being from Montana. Are Montanans known for being good at interpreting standards?Rob Kennedy
I think a certain flinty eyed steeliness and familiarity with guns is required to go up against the C++ standard.Martin Beckett
For the sake of clarity, can you also show the command line you've used to compile your code?Pavel Minaev

8 Answers

5
votes

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.

4
votes

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.

3
votes

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.

1
votes

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

1
votes

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?

0
votes

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.

0
votes

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.

0
votes

Since c++11 you could build a little template framework to give you a syntax like this:

void test(int a, int x, int y, int z)
{
    std::cout << "given " << a << ", choosing ";
    given(a)
        .when(x, [] { std::cout << "x\n"; })
        .when(y, [] { std::cout << "y\n"; })
        .when(z, [] { std::cout << "z\n"; })
        .when(any_other, [] { std::cout << "none of the above\n"; });
}

Full demo:

#include <iostream>


struct any_object {};
constexpr auto any_other = any_object {};

template<class Expr>
struct when_object
{

    template<class T, class F>
    constexpr when_object& when(T const& value, F&& f)
    {
        if (not executed and expr == value) {
            executed = true;
            f();
        }
        return *this;
    }

    template<class F>
    constexpr void when(any_object, F&& f)
    {
        if (not executed) {
            executed = true;
            f();
        }
    }

    Expr const& expr;
    bool executed = false;
};

template<class Expr>
constexpr auto given(Expr const& expr)
{
    return when_object<Expr> {expr};
}

void test(int a, int x, int y, int z)
{
    std::cout << "given " << a << ", choosing ";
    given(a)
        .when(x, [] { std::cout << "x\n"; })
        .when(y, [] { std::cout << "y\n"; })
        .when(z, [] { std::cout << "z\n"; })
        .when(any_other, [] { std::cout << "none of the above\n"; });
}


int main()
{
    test(4, 4, 5, 6);
    test(4, 3, 4, 5);
    test(4, 2, 3, 4);
    test(1, 2, 3, 4);
}

expected results:

given 4, choosing x
given 4, choosing y
given 4, choosing z
given 1, choosing none of the above