47

If you want to associate some constant value with a class, here are two ways to accomplish the same goal:

class Foo
{
public:
    static const size_t Life = 42;
};

class Bar
{
public:
    enum {Life = 42};
};

Syntactically and semantically they appear to be identical from the client's point of view:

size_t fooLife = Foo::Life;
size_t barLife = Bar::Life;

Is there any reason other than just pure style concerns why one would be preferable to another?

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • Nowadays, compared to `static const`, C++11's `static constexpr` is better as it enforces that the compiler must be able to resolve the value at compile time, which signals intent and might avoid mishaps. This also _might_ help with optimisation later, but I provide no warranty for this speculation! – underscore_d Dec 20 '15 at 11:54
  • @alfC ...and? i didn't say it wasn't, because i wasn't talking about `enum`s. – underscore_d Mar 23 '16 at 00:58

6 Answers6

65

The enum hack used to be necessary because many compilers didn't support in-place initialization of the value. Since this is no longer an issue, go for the other option. Modern compilers are also capable of optimizing this constant so that no storage space is required for it.

The only reason for not using the static const variant is if you want to forbid taking the address of the value: you can't take an address of an enum value while you can take the address of a constant (and this would prompt the compiler to reserve space for the value after all, but only if its address is really taken).

Additionally, the taking of the address will yield a link-time error unless the constant is explicitly defined as well. Notice that it can still be initialized at the site of declaration:

struct foo {
    static int const bar = 42; // Declaration, initialization.
};

int const foo::bar; // Definition.
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    Actually - your compiler shouldn't allocate any storage space for the constant. The standard explicitly states that only if the object is "used", ie. it's address needed, is the definition for the object required. And then the developer has to provide a definition explicitly! – Richard Corden Oct 16 '08 at 14:30
  • 3
    If you take the address of a "static const" but don't define it, then it should result in a linker error. – Richard Corden Oct 16 '08 at 14:32
  • 1
    Richard, you're mixing intialization with definition here; you can well initialize the constant inline and define it separately. But you're right, I need to clarify my posting. – Konrad Rudolph Oct 17 '08 at 07:19
  • 1
    Using your example with VS2010, the linker complains that foo::bar has a duplicate definition (even if I add code that explicitly takes the address of foo:bar). Is that a compiler bug? Does it make a difference that foo is part of a static library? – Adrian McCarthy Dec 07 '11 at 13:53
  • 1
    @Adrian Simple, you are probably defining the constant in the header and use it in several compilation units. You cannot do that, for the usual reasons of the One Definition Rule. You need to define the constant in the implementation file instead of the header. – Konrad Rudolph Dec 07 '11 at 14:00
  • Older Apple `ld`'s may need the `enum` to avoid linker errors like *Undefined symbols: `::$non_lazy_ptr`*. Also see [OS X 10.5 and Missing symbols for FixedKeyLength::KEYLENGTH](http://github.com/weidai11/cryptopp/issues/255). I spent a day trying to get the `static const` to work; while the `enum` worked immediately. – jww Sep 06 '16 at 07:51
  • 1
    @jww Is this really just an issue on older compilers? The code in question simply had a bug: it was trying to externally reference an undefined (and hence unexported) symbol. The correct fix would have been to define (rather than *use*, [as suggested](https://github.com/weidai11/cryptopp/issues/255#issuecomment-244839274)) the symbol. So the upshot is: if you want to make a symbol definition visible, you … uh, need to *define* it. See the last paragraph + code of my answer. – Konrad Rudolph Sep 06 '16 at 11:48
  • *"Is this really just an issue on older compilers..."* - It was limited to older Apple `ld`'s. As far as I know, the symbol was defined along with `MIN_KEYLENGTH`, `MAX_KEYLENGTH`, etc. No other compilers or linkers had trouble with it, and I tested back to VS2002 on Windows XP and GCC3 on Fedora 1. The comment you referenced did ***not*** work (it was my first try to clear it). Checkout some of the problems associated with that `non_lazy_ptr` symbol decoration. – jww Sep 06 '16 at 11:55
  • @jww That's my point: the attempt didn't work, but defining the symbol should work. I'm puzzled that the original code works on other linkers. There simply is no symbol defined so it cannot be exported. – Konrad Rudolph Sep 06 '16 at 12:23
  • @KonradRudolph - *"See the last paragraph + code of my answer...."* - Oh, my bad. I see what you mean. Let me try that once I finish this round of testing. So I am clear... Is it safe to place the code/definition in a CPP file rather than an H file? – jww Sep 06 '16 at 12:28
  • 1
    @jww Yes, the definition must go into the cpp file, otherwise you'll get duplicate definitions. – Konrad Rudolph Sep 06 '16 at 12:36
  • @KonradRudolph - Got it, thanks. That's what I thought. I'm not sure where/when I made the leap an explicit instantiation creates the definitions, too. Maybe I was getting lucky and took it for granted; and then ran into a conforming compiler where the assumptions did not hold. I'll have to think about that for a while.... – jww Sep 06 '16 at 12:38
  • Thanks again @KonradRudolph. You were right; I experienced the issue again during testing on Ubuntu 12.04 with GCC 4.6. I also observed it on CentOS 5 with GCC 4.1. But I'm also beginning to see a pattern: the issue surfaces under debug builds with `-g3 -O0`, and not release builds with `-g2 -O2`. – jww Sep 07 '16 at 11:27
11

They're not identical:

size_t *pLife1 = &Foo::Life;
size_t *pLife2 = &Bar::Life;
  • 3
    Essentially what I said, in less words. ;-) – Konrad Rudolph Oct 15 '08 at 14:46
  • 3
    Code sample should read (with added `const`s): `const size_t *pLife1 = &Foo::Life;` `const size_t *pLife2 = &Bar::Life;` The first is ok, but the second yields a compiler error, since the identifiers in an enumerator list are declared as constants, and you cannot take the address of a constant. – boycy Aug 13 '12 at 08:36
9

One difference is that the enum defines a type that can be used as a method parameter, for example, to get better type checking. Both are treated as compile time constants by the compiler, so they should generate identical code.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
7

static const values are treated as r-values just like enum in 99% of code you'll see. Constant r-values never have memory generated for them. The advantage enum constants is they can't become l-values in that other 1%. The static const values are type safe and allow for floats, c-strings, etc.

The compiler will make Foo::Life an l-value if it has memory associated with it. The usual way to do that is to take its address. e.g. &Foo::Life;

Here is a subtle example where GCC will use the address:

int foo = rand()? Foo::Life: Foo::Everthing;

The compiler generated code uses the addresses of Life and Everything. Worse, this only produces a linker error about the missing addresses for Foo::Life and Foo::Everything. This behavior is completely standard conforming, though obviously undesirable. There are other compiler specific ways that this can happen, and all standard conforming.

Once you have a conforming c++11 compiler the correct code will be

class Foo {
 public:
  constexpr size_t Life = 42;
};

This is guaranteed to always be an l-value and it's type-safe, the best of both worlds.

deft_code
  • 57,255
  • 29
  • 141
  • 224
  • 2
    Did you mean "guaranteed to always be an _rvalue_"? That's a lot more likely than what you actually wrote, but it's still not guaranteed. A `constexpr` can be allocated storage if its address is taken, in which case it becomes equivalent to a `const` variable after initialisation. But sure, if you don't do that, then `constexpr` is storage-free and the optimal solution for various reasons. – underscore_d Apr 20 '16 at 19:59
  • 2
    `constexpr size_t Life = 42;` will cause a compile error. It needs to be static, too. That's according to Clang 3.8. – jww Sep 06 '16 at 12:56
  • 1
    "Here is a subtle example where GCC will use the address" [doesn't apply with GCC 5.1](http://ideone.com/iNXUog). Even taking a reference to a non-`constexpr`, `static const` doesn't *actually* take an address unless you try to take the address of the reference. Not sure how other compilers handle this, but you made specific mention of GCC. – monkey0506 Nov 14 '16 at 23:46
5

Well, if needed, you can take the address of a static const Member Value. You've have to declare a separate member variable of enum type to take the address of it.

James Curran
  • 101,701
  • 37
  • 181
  • 258
5

Another third solution?

One subtle difference is that the enum must be defined in the header, and visible for all. When you are avoiding dependencies, this is a pain. For example, in a PImpl, adding an enum is somewhat counter-productive:

// MyPImpl.hpp

class MyImpl ;

class MyPimpl
{
   public :
      enum { Life = 42 } ;
   private :
      MyImpl * myImpl ;
}

Another third solution would be a variation on the "const static" alternative proposed in the question: Declaring the variable in the header, but defining it in the source:

// MyPImpl.hpp

class MyImpl ;

class MyPimpl
{
   public :
      static const int Life ;
   private :
      MyImpl * myImpl ;
}

.

// MyPImpl.cpp
const int MyPImpl::Life = 42 ;

Note that the value of MyPImpl::Life is hidden from the user of MyPImpl (who includes MyPImpl.hpp).

This will enable the MyPimpl author to change the value of "Life" as needed, without needing the MyPImpl user to recompile, as is the overall aim of the PImpl.

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159