234

I want to have a static const char array in my class. GCC complained and told me I should use constexpr, although now it's telling me it's an undefined reference. If I make the array a non-member then it compiles. What is going on?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Pubby
  • 51,882
  • 13
  • 139
  • 180
  • 1
    Just a hunch, does it work if baz is int for example? Can you then access it? It could also be a bug. – FailedDev Nov 04 '11 at 23:19
  • 1
    @Pubby: Question: Which translation unit will it be defined in? Answer: Everything that include the header. Problem: Violates the one definition rule. Exception: Compile-time constant integrals can be "initialized" in headers. – Mooing Duck Nov 04 '11 at 23:20
  • It compiles fine as an `int` @MooingDuck It works fine as a non-member. Wouldn't that violate the rule too? – Pubby Nov 04 '11 at 23:22
  • @Pubby8: `int`s cheat. As a non-member, that shouldn't be allowed, unless the rules changed for C++11 (possible) – Mooing Duck Nov 04 '11 at 23:29
  • Considering the views and upvotes, this question required a more detailed answer, which I added below. – Shafik Yaghmour Mar 04 '15 at 04:20
  • @BenVoigt that can not possibly be a duplicate, as my answer shows this case is particular to C++11 and the duplicate is pre-C++11. – Shafik Yaghmour Mar 04 '15 at 05:04
  • @Shafik: It is a duplicate, as the rule that static members which are odr-used require a definition, not merely a declaration, precedes C++11. – Ben Voigt Mar 04 '15 at 05:05
  • The rules around initializing in class const static members changed a lot and so did the odr rules. This particular case is not even possible before C++11 and so the answer to this question can not be found in a C++03 question. – Shafik Yaghmour Mar 04 '15 at 13:06
  • Especially since the duplicate actually deals with the difference between using the member variable and using the result of the cast which have different results. So the main meat of the question is radically different from this question. – Shafik Yaghmour Mar 04 '15 at 14:18
  • If you believe this question should be re-opened, please open a meta question about it. – George Stocker Apr 02 '15 at 12:52

6 Answers6

235

Add to your cpp file:

constexpr char foo::baz[];

Reason: You have to provide the definition of the static member as well as the declaration. The declaration and the initializer go inside the class definition, but the member definition has to be separate.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 83
    That looks weird... since it doesn't seem to provide compiler with some information it had not before... – vines Sep 19 '12 at 23:13
  • 39
    It looks even more weird when you have your class declaration in .cpp file! You initialize the field in the class declaration, but you still need to "_declare_" the field by writing constexpr char foo::baz[] below the class. It seems that programmers using constexpr can compile their programs by following one weird tip: declare it again. – Lukasz Czerwinski Mar 19 '14 at 18:36
  • 5
    @LukaszCzerwinski: The word you're looking for is "define". – Kerrek SB Mar 19 '14 at 19:13
  • 5
    Right, no new information: declare using `decltype(foo::baz) constexpr foo::baz;` – not-a-user Jul 04 '14 at 10:22
  • Will this place a copy of the "quz" data in every source file? – Neil Kirk Aug 15 '14 at 09:26
  • @NeilKirk: That's unspecified. `foo::baz` is initalized with `"quz"`, but the initializer is of course visible to everyone who includes the header. It's up to the compiler whether it always issues a load from the stored variable, or if it folds constants when appropriate. – Kerrek SB Aug 15 '14 at 09:58
  • OK in that case I will avoid using this in case it increases exe size. – Neil Kirk Aug 15 '14 at 10:27
  • 1
    @NeilKirk: I didn't say that and that's not what's happening. "Visible in a translation unit" does not mean "will add data to the translated output". Don't second-guess, write naturally. If anything, constant folding will make the code shorter since a fetch instruction can be replaced by an immediate value. – Kerrek SB Aug 15 '14 at 10:28
  • I know that if you put `const std::string x = "hello"; in a header file, it will create a copy of the string in every source file which includes it. I was wondering if this causes the same problem. – Neil Kirk Aug 15 '14 at 10:41
  • 1
    @NeilKirk: That's a completely different scenario. You're describing a global variable with internal linkage. – Kerrek SB Aug 15 '14 at 11:16
  • Yes. I'm new to constexpr so that's why I asked. What would `static constexpr std::string baz = "quz";` in a header file do? – Neil Kirk Aug 15 '14 at 11:54
  • 3
    @NeilKirk: This discussion is getting very confused. `constexpr` is yet a different, unrelated thing, not immediately connected with linkage. – Kerrek SB Aug 15 '14 at 12:21
  • Just an FYI: I tried this and it segfaulted Clang 3.5.1 – getting rid of it (e.g. trying something like Pubby’s original example) compiled fine… Segfaulting the stable release version of any compiler always gives one pause, so I thought I’d mention it here, YMM (and surely will) V – fish2000 Apr 18 '15 at 22:45
  • 9
    what will the expression look like if foo is templatized? thanks. – Hei Jul 13 '15 at 14:22
  • Why is this not required if the type of the static constexpr is double and not char[]? – Felix Crazzolara Oct 16 '19 at 18:30
  • 1
    @FelixCrazzolara: It is generally related to the extremely subtle notion of odr-use. It's sometimes possible to not-odr-use a double, but much harder to not-odr-use an array. But the more robust answer is that it _is_ always required, in both cases. (But note also that C++17 changes the picture a bit thanks to `inline` variables.) – Kerrek SB Oct 16 '19 at 23:37
  • 1
    Thanks for the useful answer! This question and answer, together with the C++17 answer below, is a manifest on how arbitrarily complex C++ really is. In C++17 the definition and declaration roles of these two statements are _swapped_! Can we please make it more confusing! :-) Why didn't in-class data get weak linkage like all inline functions do. This would have solved the problem and nobody would have even thought about it. – Johannes Overmann Jan 17 '20 at 19:55
  • @JohannesOvermann: A classic problem of lack of time machines and unreasonably higher quality of hindsight than foresight, I guess... We do have the feature now, but it's not the default. – Kerrek SB Jan 25 '20 at 22:06
124

C++17 introduces inline variables

C++17 fixes this problem for constexpr static member variables requiring an out-of-line definition if it was odr-used. See the second half of this answer for pre-C++17 details.

Proposal P0386 Inline Variables introduces the ability to apply the inline specifier to variables. In particular to this case constexpr implies inline for static member variables. The proposal says:

The inline specifier can be applied to variables as well as to functions. A variable declared inline has the same semantics as a function declared inline: it can be defined, identically, in multiple translation units, must be defined in every translation unit in which it is odr­-used, and the behavior of the program is as if there is exactly one variable.

and modified [basic.def]p2:

A declaration is a definition unless
...

  • it declares a static data member outside a class definition and the variable was defined within the class with the constexpr specifier (this usage is deprecated; see [depr.static_constexpr]),

...

and add [depr.static_constexpr]:

For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the class with no initializer. This usage is deprecated. [ Example:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 — end example ]


C++14 and earlier

In C++03, we were only allowed to provide in-class initializers for const integrals or const enumeration types, in C++11 using constexpr this was extended to literal types.

In C++11, we do not need to provide a namespace scope definition for a static constexpr member if it is not odr-used, we can see this from the draft C++11 standard section 9.4.2 [class.static.data] which says (emphasis mine going forward):

[...]A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. —end note ] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

So then the question becomes, is baz odr-used here:

std::string str(baz); 

and the answer is yes, and so we require a namespace scope definition as well.

So how do we determine if a variable is odr-used? The original C++11 wording in section 3.2 [basic.def.odr] says:

An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.

So baz does yield a constant expression but the lvalue-to-rvalue conversion is not immediately applied since it is not applicable due to baz being an array. This is covered in section 4.1 [conv.lval] which says :

A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue.53 [...]

What is applied in the array-to-pointer conversion.

This wording of [basic.def.odr] was changed due to Defect Report 712 since some cases were not covered by this wording but these changes do not change the results for this case.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • so are we clear that `constexpr` has absolutely nothing to do with it? (`baz` is a constant expression anyway) – M.M Mar 04 '15 at 04:23
  • @MattMcNabb well *constexpr* is required if the member is not a `integral or enumeration type` but otherwise, yes, what matters is that it is a *constant expression*. – Shafik Yaghmour Mar 04 '15 at 04:35
  • In the first paragraph the "ord-used" should read as "odr-used", I believe, but I am never sure with C++ – Egor Pasko May 20 '19 at 13:49
55

This is really a flaw in C++11 - as others have explained, in C++11 a static constexpr member variable, unlike every other kind of constexpr global variable, has external linkage, thus must be explicitly defined somewhere.

It's also worth noting that you can often in practice get away with static constexpr member variables without definitions when compiling with optimization, since they can end up inlined in all uses, but if you compile without optimization often your program will fail to link. This makes this a very common hidden trap - your program compiles fine with optimization, but as soon as you turn off optimization (perhaps for debugging), it fails to link.

Good news though - this flaw is fixed in C++17! The approach is a bit convoluted though: in C++17, static constexpr member variables are implicitly inline. Having inline applied to variables is a new concept in C++17, but it effectively means that they do not need an explicit definition anywhere.

SethML
  • 701
  • 5
  • 2
6

My workaround for the external linkage of static members is to use constexpr reference member getters (which doesn't run into the problem @gnzlbg raised as a comment to the answer from @deddebme).
This idiom is important to me because I loathe having multiple .cpp files in my projects, and try to limit the number to one, which consists of nothing but #includes and a main() function.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
Josh Greifer
  • 3,151
  • 24
  • 25
  • nice idea. I also saw in another post, that an alternative for integer literals is to define an unscoped enum in the class, with the main downside being that it defines a class-scoped value, which can't be accessed as a member. see stackoverflow.com/a/3026072/937363 – mattgately Apr 15 '22 at 19:38
5

Isn't the more elegant solution be changing the char[] into:

static constexpr char * baz = "quz";

This way we can have the definition/declaration/initializer in 1 line of code.

Neel Basu
  • 12,638
  • 12
  • 82
  • 146
deddebme
  • 445
  • 1
  • 5
  • 10
  • 12
    with `char[]` you can use `sizeof` to get the length of the string at compile time, with `char *` you cannot (it will return the width of the pointer type, 1 in this case). – gnzlbg May 10 '16 at 10:52
  • 3
    This also generates warning if you want to be strict with ISO C++11. – Shital Shah Jul 12 '17 at 04:11
  • See my answer which doesn't exhibit the `sizeof` issue , and can be used in "header-only" solutions – Josh Greifer Feb 28 '19 at 14:24
  • 1
    Add const to fix the ISO warning: static constexpr const char * baz = "quz"; – mentalmushroom Sep 16 '20 at 15:11
  • Yeah, you need to add `const` or the `left` / `right` side of `char` in order to mark the pointed value as const (which is a string literal, so it will give undefined behavior if you try to change it). The constexpr in your case will make only the pointer `const`. That's another reason why I prefer `constexpr char s[]` to `constexpr const char* s` or `constexpr char const* s`. – codentary Nov 30 '21 at 19:21
-2

On my environment, gcc vesion is 5.4.0. Adding "-O2" can fix this compilation error. It seems gcc can handle this case when asking for optimization.