9

I have the following working code:

#include <string>
#include <iostream>

class A {
public:
  const std::string test = "42";
  //static const std::string test = "42"; // fails
};

int main(void){
  A a;
  std::cout << a.test << '\n';
}

Is there a good reason why it is not possible to make the test a static const ? I do understand prior to c++11 it was constrained by the standard. I thought that c++11 introduced in-class initializations to make it a little bit friendlier. I also not such semantic are available for integral type since quite some time.

Of course it works with the out-of class initialization in form of const std::string A::test = "42";

I guess that, if you can make it non-static, then the problem lies in one of the two. Initializing it out-of-class scope (normally consts are created during the instantiation of the object). But I do not think this is the problem if you are creating an object independant of any other members of the class. The second is having multiple definitions for the static member. E.g. if it were included in several .cpp files, landing into several object-files, and then the linker would have troubles when linking those object together (e.g. into one executable), as they would contain copies of the same symbol. To my understanding, this is exactly equal to the situation when ones provides the out-of-class right under the class declaration in the header, and then includes this common header in more than one place. As I recall, this leads to linker errors.

However, now the responsibility of handling this is moved onto user/programmer. If one wants to have a library with a static they need to provide a out-of-class definition, compile it into a separate object file, and then link all other object to this one, therefore having only one copy of the binary definition of the symbol.

I read the answers in Do we still need to separately define static members, even if they are initialised inside the class definition? and Why can't I initialize non-const static member or static array in class?.

I still would like to know:

  1. Is it only a standard thing, or there is deeper reasoning behind it?
  2. Can this be worked-around with the constexpr and user-defined literals mechanisms. Both clang and g++ say the variable cannot have non-literal type. Maybe I can make one. (Maybe for some reason its also a bad idea)
  3. Is it really such a big issue for linker to include only one copy of the symbol? Since it is static const all should be binary-exact immutable copies.

Plese also comment if I am missing or missunderstanding something.

Community
  • 1
  • 1
luk32
  • 15,812
  • 38
  • 62
  • When it comes to statics, you can only initialize `const` integral types and enums at the point of declaration. – juanchopanza Jun 27 '13 at 19:46
  • @juanchopanza I know it. I thought in c++11 it could be overcome. – luk32 Jun 27 '13 at 19:49
  • I think that you pointed out in 3. one of the problems. Yes it would be doable, but it would require this case to be special-cased, as otherwise the linker just does not "like" multiple definitions. – ondrejdee Jun 27 '13 at 19:50
  • I never understood the rationale for any of these restrictions in the first place. The problem of multiply-defined identical symbols also applies to template instantiation, so linkers already know how to deal with it (by throwing away all copies except one). – Nemo Jun 27 '13 at 19:54
  • @ondav If its `const static` is really it "multiple". Is it not only the copies? Why not pick one? I am not very familiar with linker in'n'outs, address translations and stuff. Maybe someone knows why is it so hard or discarded idea. Honestly, previously I thought it is the problem with in-class initialization more than multiple symbols, but it seems its not the case. – luk32 Jun 27 '13 at 19:55

1 Answers1

8

Your question sort of has two parts. What does the standard say? And why is it so?

For a static member of type const std::string, it is required to be defined outside the class specifier and have one definition in one of the translation units. This is part of the One Definition Rule, and is specified in clause 3 of the C++ standard.

But why?

The problem is that an object with static storage duration needs unique static storage in the final program image, so it needs to be linked from one particular translation unit. The class specifier doesn't have a home in one translation unit, it just defines the type (which is required to be identically defined in all translation units where it is used).

The reason a constant integral doesn't need storage, is that it is used by the compiler as a constant expression and inlined at point of use. It never makes it to the program image.

However a complex type, like a std::string, with static storage duration need storage, even if they are const. This is because they may need to be dynamically initialized (have their constructor called before the entry to main).

You could argue that the compiler should store information about objects with static storage duration in each translation unit where they are used, and then the linker should merge these definitions at link-time into one object in the program image. My guess for why this isn't done, is that it would require too much intelligence from the linker.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 1
    "The reason a constant integral doesn't need storage, is that it is used by the compiler as a constant expression and inlined at point of use. It never makes it to the program image." Except I can write `&const_int_member` which requires storage. And a static constant int to which I never take a reference does not need storage. Finally, templates require exactly the same "too much intelligence" from the linker. So as far as I can tell this entire argument is nonsense in every detail. (Not that you are alone in making it...) – Nemo Jun 27 '13 at 22:03
  • @Nemo: Performing indirection (`&const_int_member`) causes it to become odr-used, and therefore the requirement noted in 9.4.2p4 come into play, that being that "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." (ie it now needs one definition) As for templates, they have have special _instantiation units_ that use special rules in translation phase 8. – Andrew Tomazos Jun 27 '13 at 22:18
  • OK, but as soon as you make it "odr-used", it has all the same issues as any user-defined type. So the rationale still falls apart; there is simply no reason not to let any type be initialized in the header, even if you still require a single definition somewhere in namespace scope. This distinction between "const int" and "const whatever" really makes no sense when you think it through, as far as I can tell... – Nemo Jun 28 '13 at 04:04
  • @Nemo: No, it does not have the same issues. As I said previously constant integral types can be inlined (whether odr-used or not, and whether they have storage or not), whereas user-defined types cannot be in all cases as they require dynamic initialization and storage. – Andrew Tomazos Jun 28 '13 at 05:33
  • So, most probably no workaround with designing a nifty user-defined literal. I already knew, that integral was a really special exception, only because it will get inlined into assembly calls, and will not need storage in the final objects. Thus you cannot dereference it, while you could if it were defined outside of the class. I think the other answers even had examples. I also know the 9.4.2. I just thought in c++11 they made it possible somehow. My question seems kinda redundant now. Maybe someone will explain why compilers cannot perform the seemingly simple job, if not I accept this. – luk32 Jun 28 '13 at 09:50