32

This is maybe a basic question, but I cannot see the response by myself right now.

Consider the following code:

template<bool b>
struct T {
    static constexpr int value = (b ? 42 : 0);
};

template<bool b>
struct U {
    enum { value = (b ? 42 : 0) };
};

int main() {
    static_assert(T<true>::value == 42, "!");
    static_assert(T<false>::value == 0, "!");
    static_assert(U<true>::value == 42, "!");
    static_assert(U<false>::value == 0, "!");
}

I'm used to using structs like T, but more than once I've seen structs like U used for the same purpose (mostly traits definition).

As far as I can see, they are both resolved at compile time and they solve almost the same problem, but it seems to me that T is far more readable than U (well, I know, my personal opinion).

My question is pretty simple: is there any technical reason for which one solution is better than the other one?
Even more, is there any case for which one of them is not a viable solution?

Eissa N.
  • 1,695
  • 11
  • 18
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    If you were using something other than integrals then you'd HAVE to use constexpr. – Edward Strange May 16 '16 at 17:48
  • 1
    @CrazyEddie This is quite obvious!! The example is not there for a mistake. :-) – skypjack May 16 '16 at 18:17
  • 1
    I'd like to know the reasons of the downvoters and the ones that voted to close the question as primarily opinion-based, with such a technical answer posted by @SergeyA (as correctly pointed out in the question for its technical parts, indeed). Quite funny. – skypjack May 16 '16 at 19:18

3 Answers3

31

Please note, answer below is not applicable for C++ 17 and later.

There will be no noticeable difference for integral constants when used like this.

However, enum is actually better, because it is a true named constant. constexpr integral constant is an object which can be, for example, ODR-used - and that would result in linking errors.

#include <iostream>

struct T {
    static constexpr int i = 42;
    enum : int {x = 42};
};

void check(const int& z) {
    std::cout << "Check: " << z << "\n";
}

int main() {
    // check(T::i); // Uncommenting this will lead to link error
    check(T::x);
}

When check(T::i) is uncommented, the program can not be linked:

/tmp/ccZoETx7.o: In function `main': ccc.cpp:(.text+0x45): undefined reference to `T::i' collect2: error: ld returned 1 exit status

However, the true enum always works.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Wow, it sounds interesting. Thank you! Would it be possible to edit the answer and add an example for which the `constexpr` does not compile? – skypjack May 16 '16 at 17:44
  • @skypjack, it will always compile, but it will fail to link. I will provide an example. – SergeyA May 16 '16 at 17:45
  • Right, you said that. Sorry, anyway you got it. :-) – skypjack May 16 '16 at 17:46
  • Thank you very much for the details. Probably I would have never got it by myself. Really interesting. – skypjack May 16 '16 at 18:23
  • 1
    You can work around the issue by instantiating `i` in a cpp file, but then that allows you to take the address of `&`. Taking the address of a value is nonsensical. Enums don't allow this. – Mooing Duck May 16 '16 at 20:19
  • What do you mean by ODR-used? – Ben Collins May 18 '16 at 00:20
  • 1
    @BenCollins You can see [3.2p3](http://eel.is/c++draft/basic.def.odr#3) or any other site like [this one](http://en.cppreference.com/w/cpp/language/definition). – skypjack May 18 '16 at 05:49
  • @MooingDuck Can I really define a `static constexpr` in a _cpp_ file to work around the issue? I guessed it was not possible for such a declaration... – skypjack May 18 '16 at 05:51
  • @ixSci Never tried it before, just an assumption. Thanks, I learned something new today morning. :-) – skypjack May 18 '16 at 06:28
  • I think C++17 will have `inline` variables to allow this without ODR issues. – gnzlbg May 18 '16 at 09:02
  • 1
    @gnzlbg `inline` variable? Never heard about them. Have you a link? Thanks. – skypjack May 18 '16 at 09:50
  • 2
    @skypjack [N4424](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4424.pdf) – Barry May 18 '16 at 14:29
  • @Barry Thank you very much, really appreciated. – skypjack May 18 '16 at 14:32
  • @cqdjyy01234, than vs2015 has a bug (or you succeffully linked something else). – SergeyA May 18 '16 at 14:43
  • @SergeyA, it is not a bug. Diagnostic is not required for such cases. – ixSci May 19 '16 at 05:42
  • @ixSci, it is not required, but it is unvoidable if standard is to be followed to letter. You can't avoid taking address, and to take address you need a global symbol. – SergeyA May 19 '16 at 13:27
  • @SergeyA, it is just a UB, that's all. So MSVC has its right to not produce any errors. – ixSci May 19 '16 at 13:38
  • @ixSci, well, UB is Standard term. But, standard mandates several things to happen (for example, it mandates that result of addressof of the argument would be the same in all translation units). It is simply not possible to do without having a real global symbol there. And it is not possible to have a real symbol without definiton. So if it links, it means that this rule is violated. – SergeyA May 19 '16 at 13:40
  • 1
    @SergeyA, _"If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program."_ – ixSci May 19 '16 at 13:43
  • @ixSci, you are not listening to me. I am not saying it violates the standard in 'no diagnostic is required'. I am saying, that knowing a tiny bit about how compilers actually work, I conclude that successful linking is only possible if other provisions of standard are violated. – SergeyA May 19 '16 at 13:51
  • 1
    @SergeyA, but according to the Standard the conformant implementation may do _whatever_ it wants including violating of any other parts of the Standard. It can do _anything_. Even produce a completely different program from what has been provided to it to compile. – ixSci May 19 '16 at 14:10
  • @ixSci, I would agree with you - if I'd expect MSVC to be a human being rather than compiler which in all honesty just generates ASM code based on the C++ code. :) On any rate, I think we are disputing very boring and insiginificant point )). – SergeyA May 19 '16 at 14:16
5

I suspect it's legacy code.

enum { value = (b ? 42 : 0) };

is valid code in C++03 as well as C++11.

static constexpr int value = (b ? 42 : 0);

is valid only in C++11.

Even more, is there any case for which one of them is not a viable solution?

Both are viable solutions in C++11. The choice of which one to use depends on a team. It's going to be a matter of a policy decision.

As the answer by SergeyA indicates, enum are true constants. You cannot ODR-use them. You can ODR-use a constexpr. Depending on which of these is desirable for your application, you can decide whether to use enums or constexprs.

Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
5

The currently accepted answer by SergeyA no longer holds as of C++17 (Definitions and ODR).

Every declaration is a definition, except for the following:

  • ...
  • (deprecated) Namespace scope declaration of a static data member that was defined within the class with the constexpr specifier
struct S {
    static constexpr int x = 42; // implicitly inline, defines S::x
};
constexpr int S::x; // declares S::x, not a redefinition

Hence, as of C++17, I would use the static constexpr definition which is more expressive than the enum.

Touloudou
  • 2,079
  • 1
  • 17
  • 28