C++ Switch won't compile with externally defined variable used as case
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 n开发者_运维百科ot 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)
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.
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.
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.
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
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?
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.
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.
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
精彩评论