22

Unfortunately, I am somewhat confused about constexpr, global constants declared in header files, and the odr.

In short: Can we conclude from here

https://isocpp.org/files/papers/n4147.pdf

that

constexpr MyClass const MyClassObj () { return MyClass {}; }
constexpr char const * Hello () { return "Hello"; }

is preferable over

constexpr MyClass const kMyClassObj = MyClass {};
constexpr char const * kHello = "Hello";

for defining globals in a header file if I want to "just use" those globally declared/defined entities and do not want to think about how I use them?

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
JohnB
  • 13,315
  • 4
  • 38
  • 65
  • Constants at namespace scope have internal linkage. The problem arises when you evaluate such constants in `inline` functions. – Kerrek SB Dec 23 '15 at 23:48
  • What platform are you on? – Nir Friedman Dec 24 '15 at 03:29
  • 1
    @JohnB: You may want to mark it `constexpr char const * const kHello = "Hello";` instead -- in c++11, `constexpr` implies `const`, but in c++14 it doesn't. I'm not sure if the ODR is substantially different in c++14 on this issue, but anyways you can get a compiler warning about this, in my experience. – Chris Beck Dec 24 '15 at 06:29
  • 6
    @ChrisBeck You are confusing `constexpr` on member functions and `constexpr` on variables. – T.C. Dec 24 '15 at 06:39
  • @Nir Friedman: VS2015. – JohnB Dec 24 '15 at 08:28
  • 1
    This is just an academic issue. In practice nothing wrong is going to happen, because (for the cases where calls to an `inline` function are not inlined) the linker just selects one arbitrary (inline) function definition among the discardable multitude, and that single one *does what you want*. Except if you intentionally introduce a dependency on address of constant or such silliness. In other words, no practical problem. – Cheers and hth. - Alf Dec 24 '15 at 12:26
  • @Cheersandhth.-Alf Exactly, I would never ever think of passing a constant to a forwarding function template – Columbo Dec 24 '15 at 12:29
  • @Columbo: Not sure what you mean. Do you think there's a practical problem with passing a constant defined in a header, to a function template? – Cheers and hth. - Alf Dec 24 '15 at 12:33
  • 1
    @Cheersandhth.-Alf Forwarding function template. Yes, there is, since that would constitute an odr-use. (If you do it inside e.g. an inline function in a header.) – Columbo Dec 24 '15 at 12:35
  • @Columbo: Well if you can provide an example (of the complete minimal kind) where things go wrong in practice, and the code isn't silly, I'll change my view. – Cheers and hth. - Alf Dec 24 '15 at 12:37
  • @ChrisBeck `constexpr` on a variable always implies `const`, no need to type the latter (unless it's for a separate element of the declaration, i.e. a pointer or ref's underlying object). That is: `static MyType constexpr myVar` indicates compile-time evaluation capability but otherwise acts like `static MyType const myVar` at runtime. As mentioned, function `const` is very different, and that's what was implied by `constexpr` in C++11 but is not in C++14 (a very important change). – underscore_d Dec 24 '15 at 12:38
  • @Cheersandhth.-Alf Put [this](http://coliru.stacked-crooked.com/a/7e7c2e7bfd29c595) into a header file and include it in two TUs. (NB: Using a bog-standard for loop with indices would be fine!) – Columbo Dec 24 '15 at 12:41
  • @Columbo: I did, except 3 TUs (including a main) and it worked fine with g++ 5.1.0. Que? – Cheers and hth. - Alf Dec 24 '15 at 12:46
  • @Cheersandhth.-Alf What do you mean with "go wrong in practice"? That's interesting but not the optimal goal. We want code to be portable in the first place, and the above isn't. – Columbo Dec 24 '15 at 12:46
  • @Cheersandhth.-Alf This is a language-lawyer question. Not tagged g++, but C++. If it works for you, kudos, but that isn't something the asker or any prudent C++ programmer goes for. – Columbo Dec 24 '15 at 12:56
  • @Columbo: I see the language lawyer tag, but the OP asks for "preferable". That's an in-practice thing. The in-practice should therefore (also) be considered. And I think, for the language view, the only interesting aspect to me is why the language requires compilers to have the mechanisms in place for inline data, but lacks any way to express it directly. But anyway, not a practical problem. If you think the code isn't portable for compilers that support the syntax, you'd have to provide an **example**; at least 1 case demonstrating the problem. – Cheers and hth. - Alf Dec 24 '15 at 13:00
  • @Cheersandhth.-Alf What's the link between "preferable" and "purely practical"? AFAICS, the asker wants the solution that's preferable in terms of both practicability and theory/portability (especially since he quoted a relevant paper!). The code as is invokes undefined behavior… I wouldn't ignore that because it works on GCC and/or Clang right now (although I do agree that the common aversion against UB is partly unjustified). I'll try to think of a practically failing example. – Columbo Dec 24 '15 at 13:08
  • (Sorry, I meant to say the code as is, when included in two translation units combined into one program, invokes UB) – Columbo Dec 24 '15 at 13:27
  • 1
    @Columbo: well, anyway :) Have a Merry Christmas. It's that time again! Yikes. OK, starting preparations... – Cheers and hth. - Alf Dec 24 '15 at 14:09
  • 1
    @Cheersandhth.-Alf Absolutely! Merry Christmas to you, pig! (In the style of Tuco, if you've seen the movie your avatar is from :D) :-) – Columbo Dec 24 '15 at 14:31

1 Answers1

17

Note: as of C++17, you can declare your variables as inline.


TL;DR: If you want to be on the (very) safe side, go with constexpr functions. It isn't inherently necessary though, and certainly won't be if you're performing trivial operations on these objects and are solely interested in their value, or simply don't use them in the dangerous scenarios listed below.

The fundamental issue is that const variables at namespace scope such as yours (generally) have internal linkage ([basic.link]/(3.2)). This implies that each translation unit compiling the corresponding header will observe a different entity (i.e. symbol).

Now imagine we have a template or inline function in a header using those objects. The ODR is very precise about this scenario - [basic.def.odr]/6:

enter image description here

"initialized with a constant expression" is certainly met, since we're talking constexpr. So is "the object has the same value in all definitions of D" if you don't monkey about.

"the object isn't odr-used" is probably the only questionable condition. Basically, it requires that you don't necessitate the variables runtime existence as a symbol, which in turn implies that

  • You don't bind it to a reference (=> you don't forward it!)

  • You don't (neither explicitly nor implicitly) take its address.

The only exception to the second rule are arrays, which can be taken the address of implicitly inside a subscript operation as long as the two above rules aren't violated for the yielded glvalue.

More precisely, odr-use is governed by [basic.def.odr]/3:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

Applying l-t-r to any constexpr variable will behave as required by the first part. The second part requires that the variable be used as a value rather than an actual object; that is, it's eventually either discarded or directly evaluated, giving the above rules of thumb.

If you avoid odr-use of the variable inside inline functions, templates or the like, you're fine. But if you use the return value of a corresponding constexpr function, you won't have to worry, since prvalues are already behaving more like values/literals (not objects) and constexpr functions are inline and definitely won't violate the ODR (if you don't use constexpr variables inside there!).

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • "you've basically performed operations you couldn't have done with the return value of the corresponding constexpr function," Not really. Reference binding (to forwarding references or const lvalue references) is really the major issue. – T.C. Dec 24 '15 at 03:15
  • Hence, if I understand you correctly, my assumption that I have to think less if I use the "function returning the value" approach is correct? My problem is: I think I understand the issue, yet I do not feel fluent enough to avoid accidental mistakes. – JohnB Dec 24 '15 at 08:28
  • @T.C. Well, to be precise, the operation would be "binding a reference to an lvalue" vs "´´ to an rvalue", although I agree it's unclear :o) – Columbo Dec 24 '15 at 12:00
  • @JohnB Well, if you're reasonably careful (i.e. swiftly check every use when you introduce it inside an inline function or the like), nothing should go wrong. If you don't want to care about this at all, go with `constexpr` functions. That's ultimately your decision. – Columbo Dec 24 '15 at 12:33
  • I don't quite understand what you think is defective in [basic.def.odr]p2.2. Have you seen http://stackoverflow.com/a/23436665/ and the discussion below? – dyp Dec 25 '15 at 15:06
  • @dyp Actually, that very bullet point has been discussed thoroughly by the CWG, and is not defective. Nevermind, I adjusted the answer. – Columbo Dec 26 '15 at 13:30