22

For example, this is how I would write it, and it compiles and works just fine:

template<typename T> struct is_pointer<T*> {
  static const bool value = true;
}

Then why do some people write the less obvious

template<typename T> struct is_pointer<T*> {
  enum { value = true };
}      

instead? Is it only because the static const variable uses a byte of memory, whereas the enum doesn't?

Thomas
  • 174,939
  • 50
  • 355
  • 478

5 Answers5

25

A notable difference is in the fact that the following code compiles and links:

template<typename>
struct is_pointer { };

template<typename T>  
struct is_pointer<T*> {
  enum { value = true };
};     

void f(const bool &b) { }

int main() {
  f(is_pointer<void*>::value);
}

The following does not work instead (you get an undefined reference to value):

template<typename>
struct is_pointer { };

template<typename T>
struct is_pointer<T*> {
  static const bool value = true;
};

void f(const bool &b) { }

int main() {
  f(is_pointer<void*>::value);
}

Of course, it doesn't work unless you add somewhere the following lines:

template<typename T>
const bool is_pointer<T*>::value;

That is because of [class.static.data]/3 (emphasis mine):

If a non-volatile non-inline const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression ([expr.const]). The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer. [...]

In other terms, static const bool value = true; is a declaration, not a definition and you cannot odr-use value.
On the other side, according with [dcl.enum/1] (emphasis mine):

An enumeration is a distinct type with named constants.

Those named constants can be const referenced as shown in the example above.


As a side note, something similar applies if you use static constexpr data members in C++11/14:

template<typename T>
struct is_pointer<T*> { static constexpr bool value = true; }; 

This doesn't work as well and that's how I discovered the subtle differences between them.

I found help here on SO getting some nice hints out of the answer I've been given.
References to the standard are a plus to better explain what's going on under the hood.

Note that a static constexpr data member declaration like the one above is also a definition since C++17. Therefore you won't have to define it anymore and you'll be able to odr-use it directly instead.


As mentioned in the comments (thanks to @Yakk that confirmed this) I'm also trying to explain how it happens that the above mentioned named constants bind to a const reference.

[expr.const/3] introduces the integral constant expression and mentions unscoped enums by saying that it's implicitly converted to a prvalue.
[dcl.init.ref/5] and [class.temporary/2] do the rest, for they rule on reference binding and temporaries.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 2
    Upvoted. The OP ought to accept this answer in place of mine. – Bathsheba Oct 13 '16 at 12:18
  • 1
    C++17 inline variables solve this problem, I believe. – Yakk - Adam Nevraumont Oct 13 '16 at 12:18
  • @Bathsheba That's really similar to a question of mine and I did my researches when I asked it. A similar answer applied to my question. – skypjack Oct 13 '16 at 14:22
  • Wait, does this also mean that the `bool`s don't actually exist in memory unless they're declared outside the template definition? And yet they have a value that we can read in some cases (e.g. simple `std::cout << is_pointer::value;`)? This language is even more messed up than I thought... – Thomas Oct 13 '16 at 15:23
  • 2
    @Thomas: `static` data members indeed only exist in memory if they are defined, which is required if their address is taken (via pointers or references). It's great when it saves memory, it's annoying when the linker complains you forgot the definition *sigh*. – Matthieu M. Oct 13 '16 at 15:36
  • @Yakk Is it right saying that the `enum` eventually binds to a const reference because of [expr.const/3](http://eel.is/c++draft/expr.const#3) (it introduces _integral constant expression_ and mentions unscoped `enum`s, saying that it's implicitly converted to a _prvalue_), [dcl.init.ref/5](http://eel.is/c++draft/dcl.init.ref#5) and [class.temporary/2](http://eel.is/c++draft/class.temporary#2) (for they rules reference binding with temporaries)? – skypjack Oct 13 '16 at 17:20
  • 1
    @skypjack Seems reasonable. – Yakk - Adam Nevraumont Oct 13 '16 at 17:29
  • noob question: why it's still okay to do `is_pointer x; std::cout << x.value << std::endl;` without the out-of-line definition for `value`? – dragonxlwang Jan 16 '21 at 07:38
  • also, why C++17 makes it work for `static constexpr` for odr-used? https://onlinegdb.com/H1W3mcl1O – dragonxlwang Jan 16 '21 at 16:28
6

Is it only because the static const variable uses a byte of memory, whereas the enum doesn't?

Yes, that's the reason.

static const bool value = true;

would occupy memory, while

enum { value = true };

doesn't.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 2
    Unfortunately (?) that's not the only reason for which one is better than the other. ;-) – skypjack Oct 13 '16 at 12:04
  • 3
    `static const bool value = true;`, in a class definition, does not occupy any memory. Memory would be occupied by a corresponding out-of-class definition, but that is only necessary if `value` is *odr-used*. – M.M Nov 01 '16 at 09:29
  • As per @M.M's comment it's much more complicated and even more so since C++ '11 – Richard Corden Oct 19 '18 at 08:55
6

Yes you are correct: enum { value = true }; doesn't occupy any memory.

Furthermore, prior to C++11 it was pretty much the only way of achieving this: static const bool value = true; is only legal in a class definition from C++11 onwards. Although a constexpr might be preferred.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
1

Some people write the less obvious enum rather than static bool const because they don't realize that there are other changes they should make.

C++ requires the object to be defined if it's address is needed, for example if it's passed to this function foo:

void foo(bool const &);

However, solving the issue by defining the object is actually not the correct fix for this problem. Here are some alternatives:

  1. Small objects should not be passed by reference. The change should be to remove const & from the function signature, not add a definition for the object.

  2. Where the function signature cannot be changed, a temporary can be created explicitly in the call: foo( bool { Cls::mbr } )

  3. However, this is compile time information! Therefore foo should be a template with a T and T* overload, or be specialized with bool.

This 3rd solution has the benefit of removing an unnecessary run time check (hopefully optimized by the compiler) and also allowing for the pointer and non-pointer case to be handled independently, possibly making the code clearer.

Richard Corden
  • 21,389
  • 8
  • 58
  • 85
  • 1
    Interesting, but I'm not sure how any of this answers the question... where in the question are the "`const &`", the "function signature", the "`foo`"? – Thomas Oct 19 '18 at 10:42
  • @Thomas Thanks for highlighting this. I was, as I'm sure you guessed, referring to the content of other answers. Better practice for the answer to be self contained. – Richard Corden Oct 19 '18 at 15:54
  • Ah, the leap-of-thought that happens only in one's mind without realizing it. I know all about that. Now I understand what you were driving at, good stuff! – Thomas Oct 20 '18 at 15:09
0

It also is another symbol in every object file that includes it, to no benefit. If you use symbol folding (--gc-sections) you'll run out of separatable sections & bloat your binary.

dascandy
  • 7,184
  • 1
  • 29
  • 50