10

I'm used to definition my constants with enum { my_const = 123; }, since in classes, using static constexpr requires some code outside of the class definition (see this question). But - what about in function bodies? Lately I've been noticing people just having constexpr variables in their functions (not even bothering to const them actually), and I was wondering whether I'm a fool who's behind the times with my

int foo(int x)
{
    enum : int { bar = 456 };
    return x + bar;
}

So, my question is: Is there any benefit to using enum's within function bodies rather than constexpr variables?

Community
  • 1
  • 1
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 3
    When C++1z comes out (probably this July) and gets mainstream compiler support, it’ll be possible to just use `static constexpr` variables in a class because they’ll automatically be inline. But for functions, do you mean that you declare an `enum` inside a function for your constants? – Daniel H Apr 24 '17 at 19:02
  • @DanielH: Yes, see edit. – einpoklum Apr 24 '17 at 19:05
  • 3
    I am almost certain there’s no benefit inside a function body (non-static variables wouldn’t need any linkage, and static ones could share that of the function itself), and in fact I think that isn’t allowed by the C++ standard (even if some compilers might allow it as an extension). In particular, I can’t get g++ (version 6.3) to compile your code at all. I’m not quite certain of this to make it an answer instead of a comment, but I would be surprised if it’s wrong. – Daniel H Apr 24 '17 at 20:12
  • @DanielH: THat's because I had a typo. Try now. – einpoklum Apr 24 '17 at 20:45
  • Why would the compiler do different things? `bar` is not going to change, the comiler will optimize it anyway. – aggsol Apr 26 '17 at 06:54
  • @aggsol: If, in your function body, you write `std::cout << &bar` then: 1. This will compile and 2. The compiler will allocate space for `bar` to exist at run-time. See @Yakk's answer. – einpoklum Apr 26 '17 at 07:57

2 Answers2

18

You can accidentally or on purpose force ODR-existence of bar if it was a constexpr int bar = 456;, this is not possible with enum : int { bar = 456 };.

This may or may not be an advantage on either side.

For example

int baz(int const* ptr ) {
  if (ptr) return 7; return -1;
}
int foo(int x)
{
  // enum : int { bar = 456 };
  constexpr int bar = 456;
  return x + baz(&bar);
}

the enum version doesn't compile, the constexpr int one does. A constexpr int can be an lvalue, an enumerator (one of the listed enum constants) cannot.

The enum values aren't actually an int, while the constexpr int is actually an int. This may matter if you pass it to

template<class T>
void test(T) {
  static_assert(std::is_same<T,int>::value);
}

one will pass the test; the other will not.

Again, this could be an advantage, a disadvantage, or a meaningless quirk depending on how you are using the token.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I know what the ODR is, but I'm not sure what you mean by "ODR-existence" here. As for the enum version not compiling - AFAIK indeed it shouldn't compile. Like you said, I don't want to have an `int` variable named bar, just a value which behaves like an `int` at compile time. I'm actually a bit surprised that the second version compiles - it means that bar must have space in memory allocated to it. – einpoklum Apr 24 '17 at 20:53
  • 2
    It will only have space in memory allocated to it if the compiler decides this is necessary. If you aren’t taking the address, this is unlikely; even if the compiler wanted to store the number somewhere it would probably use a register, and it usually wouldn’t want to store the value. A `constexpr int` is, in most cases, a value that only exists at compile time. – Daniel H Apr 24 '17 at 21:01
  • @einpok ODR means something was done such that the *identity* of the variable not just value is needed. A `constexpr` value has a value that can be computed at compile time, but it *also* can have an identity; an enum value cannot have an identity. The existence of an identity is similar to "actually existing in memory" but not quite; sometimes things with identity can have their memory optimized away. "identity" is not a term from the standard, just an attempt to explain it. – Yakk - Adam Nevraumont Apr 24 '17 at 22:07
  • The term "enum value" is ambiguous, it could mean a value of an _enumeration type_ (e.g. `decltype(bar) b = bar;`), or it could mean an _enumerator_. In this context you're talking about the latter, so I'd use that term. – Jonathan Wakely Apr 25 '17 at 11:55
  • @jona fixed in the place it matters; in the other, both senses apply. – Yakk - Adam Nevraumont Apr 25 '17 at 12:32
1

A one-liner based on @Yakk's (but this is my own take):

using enum-based constants may be necessary if you cannot allow your constant to exist as a "variable" at run time . With an enum, regardless of what you do - it will have no address and no memory space taken up (and not only because of compiler optimizations which may or may not occur).

In other cases there doesn't seem to be a compelling reason to prefer one over the other.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • There's no "variable" taking up space for a constant that isn't odr-used. That's not dependent on optimisations, it's pretty much guaranteed by the language (the standard doesn't actually talk about "memory space taken up" so it doesn't say it in exactly those words). – Jonathan Wakely Apr 25 '17 at 11:31
  • Contorting your code and inventing new types (the enum) because of a misguided (IMHO) concern about wasted space is focusing on the wrong problem. [Use enumerations to represent sets of related named constants](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Renum-set), and [Avoid unnamed enumerations](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Renum-unnamed) ("Such code is not uncommon in code written before there were convenient alternative ways of specifying integer constants. Alternative: Use constexpr values instead.") – Jonathan Wakely Apr 25 '17 at 11:43
  • 1
    @JonathanWakely: The question of whether or not this is misguided depends on the context. Someone might trigger the allocation of space for the `constexpr` field, and the different memory layout may cause incompatibility with code compiled elsewhere. – einpoklum Apr 25 '17 at 17:05
  • The warning has absolutely nothing to do with me, and neither does that part of the core guidelines. And I don't know what "different memory layout" you think can be caused by odr-use of a constant, or how it can cause an incompatibility. That seems impossible to me, unless you require 100% repeatable builds with no change in the generated assembly code. But in that case there wouldn't be code changes that can cause an odr-use of the constant. – Jonathan Wakely Apr 25 '17 at 19:25
  • Also, complaining about being given coding style advice in response to a "which should I prefer ...?" question seems odd. It seems like you'd already made up your mind about which to use and only want to hear reasons in favour of that! Your answer is rather one-sided, "using enum-based constants is **preferable** if ..." and nothing like "and a constexpr variable is preferable **in all other cases**" (just a complaint about a compiler warning that says what you don't want to hear because you've already made up you mind :-) – Jonathan Wakely Apr 25 '17 at 19:31
  • @JonathanWakely: Actually, I was really complaining about the compiler warning. Sorry, this is not the right question for it. Removing it from here. – einpoklum Apr 25 '17 at 19:42
  • "But in that case there wouldn't be code changes that can cause an odr-use of the constant" - in one translation unit somebody takes the address of the constexpr variable, and in another - they don't. So in one of them it'll exist in memory and in the other it won't. – einpoklum Apr 25 '17 at 19:50
  • No. There's only one definition in the entire program, that's the one-definition rule. If it's odr-used anywhere in the program then it needs storage. If it isn't odr-used then it doesn't require storage. It doesn't make any difference how many times it's odr-used, all that matters is "zero" or "more than zero". And if it's a static data member and you don't provide a definition then they can't odr-use it, without providing (exactly one) definition for it. – Jonathan Wakely Apr 26 '17 at 15:55