I've just stumbled into this issue. Stroustrup's "The C++ Programming Language", says (p. 249): "If (and only if) you use an initialized member in a way that requires it to be stored as an object in memory, the member must be (uniquely) defined somewhere." It doesn't say what counts as that requirement. I have some code which compiles on MSVC (2008) but does not on GCC (tested with MingW 4.5.0). So evidently they disagree on what counts. My question is, who's correct? Here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class A
{
public:
staticconstint N = 1;
enum { M };
};
void f(constint& val){}
void g(int val){}
int main(int argc, constchar *argv[])
{
f(A::N); // error on GCC, works on MSVC
f(A::M); // works on both
g(A::N); // works on both
f(5); // works on both
return 0;
}
It seems to me that since a const ref should bind to an rvalue, invoking f should not count as using A::N in a way that "requires it to be stored as an object in memory". Indeed, f(5) works fine! Any insight on this?
When you declare a variable, const or not, the compiler does not have to reserve memory for it. The compiler could, for example, hold the value in a register. However, this is not always the case. First -- the obvious one -- is if the variable isn't a POD type or doesn't fit in a register -- then the compiler must allocate storage for it. Second -- the not-so-obvious one -- is if somewhere in the program you attempt to take the address of the variable. You can't take the address of a register, so the compiler must allocate memory for it.
Since gcc implements references as pointers -- and nothing in the standard says what the underlying implementation must be -- calling f() with A::N is essentially taking the address of A::N, meaning that storage must be allocated to it. But, what causes the compiler to allocate storage for a static const within a class is explicit instantiation elsewhere. eg, constint A::N; somewhere after the declaration of A. Hence the linker error on gcc. (g() is fine on gcc because it does not attempt to take the address of A::N)
And the fun thing is that since nothing in the standard mandates the underlying implementation of references, both MSVC and GCC work correctly but yield different results.
I'd say any style of code that causes compiler incompatibilities without violating the C++ standard is code to be avoided! (And, I dunno, maybe the standard should be clarified to eliminate this sort of incompatibility?)
If GCC implements references as pointers, you'd think f(5) and f(A::M) should also not work. I just discovered that if you invoke as:
f((constint)A::N);
it works fine! You cast it to the type it already is... How weird is that??
This isn't my code, but it is inspired by code I must fix/maintain. I would have used an enum too. But I try to restrain myself from tearing through the code and changing everything that's coded in a style I don't agree with. :)
Mmm, well those are good points. The only defense I have is that the standard does specifically allow the binding
of const references to temporaries. GCC must be generating temporaries for both f(5) and f(A::M). Perhaps it is
a problem with GCC?
jsmith, my question remains. I'm guessing that explicitly using enums might hinder some possible optimizations (like not allocating memory). Also, are enumerators required to allocate memory by the standard?
After some of my reading, I see a lot of enumerations used in template metaprogramming. I don't see much of a problem with using them as above, either. However, most of the other professionals I've talked to always respond with something along the lines of, "I only think of an enum for a grouping of related values." As if an enum with a single enumerator is difficult to grasp.
Well I think it's a valid point. "enum" is so named because it is intended to be used to enumerate a set of values. I think it's important that code be clear and intuitive, as well as run efficiently. Before having stumbled across this, if I'd seen someone use an enum for a purpose other than enumerating something, I'd have scratched my head and wondered why he did it that way. I prefer that no one scratch their head when looking at my code, and wonder why I did something, even though it is easy to see what it does.
The compiler is evidently smart enough to introduce temporaries when passing some types of compile-time constants into a function that takes a (const) reference. It's inconsistent and counterintuitive to me that it doesn't in all cases. This really seems to me like one of those cases where it makes sense to do so. Then again, you can only declare an integral constant this way, not a float, double, etc... so the fact that you can do it at all seems rather special-purpose and inconsistent with the rest of the language.
What I've taken away from this is that defining int constants that way is fraught with problems, so it's best avoided if you care about portability to other compilers.
I wouldn't think enums would hinder optimizations. An enum as shown in your above example is nothing more than a declaration, so no memory is allocated for it. In template metaprogramming, this is an advantage -- template metaprogrammers want to limit the memory footprint of the template instantiations as much as possible because once you're templated, template instantiations may explode in number -- just look at boost::spirit or boost::lambda for example. This need for limiting memory footprint is why the empty base class optimization is so important.
An enumerator is a value, not a variable, so afaik a copy may be stored in memory somewhere depending on how you use it, or as a compile-time constant, it may be baked into the compiled code... maybe I misunderstood the question. It works to pass it into f in my original code snippet because the compiler was smart enough to call into being a temporary "variable" to store the value in, and passes a reference to that. It does the same thing with the f(5) call. And, imho, GCC should be smart enough to do it for the f(A::N) call too, but it apparently isn't.
I think GCC's behavior is a little inconsistent when it comes to handling the pass-by-const-reference of a static const int. GCC handles the enum as we'd expect -- it generates a temporary variable containing the value of the enum and then passes a const reference to it. I'm calling into question GCC's handling of the static const.
@chisholm:
enum { FOO = 42 }; is nothing more than a scoped #define. #define'd symbols don't occupy memory in and of themselves; it is where the symbol gets used that matters. I'd expect that code like this:
int x = FOO;
to get compiled into something like this:
mov 42, [%edx]
assuming %EDX contains the address of x. The point is... the 42 is hard-coded within the instruction; it is not occupying memory in a data segment.
Well all I meant was that if you chose to do 'int x = A::M;' in your code somewhere, then you'd have (a copy of) that value sitting in memory. So you could copy it into memory if you wanted to. Maybe the blinding obviousness of that statement caused confusion ;)