-3

I have a big project and work on refactoring it. Major task is rewrite of logger. New logger is (as far as I can tell) API-compatible with old one, so I believed that after changing header include directory, recompile and relink everything should work. But no. I get multiple errors of the kind undefined reference to <static_data_member>. I can't paste actual code, but it looks for example like this:

// Foo.h
class Foo {
    static const int bar = 0;
    int baz; // assigned in c-tor
    void updateBaz() { baz = bar; }
    // ....
}

static const int bar is NOT defined in Foo.cpp. It is sometimes printed by log macros. And it used to work (with old logger), now I have to define it. What change could have caused it?

Another example that that occurs with variables declared by boost:

(...)/blog_adaptor.h:50: error: undefined reference to bbost::serialization::version<CA::CReyzinSignature>::value'

So: when are definitions to static members required and when can they be omitted?

MateuszL
  • 2,751
  • 25
  • 38
  • https://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix – Jesper Juhl Aug 24 '18 at 11:49
  • 4
    "_I can't paste actual code, but it looks for example like this_" We **don't** require actual code. What we do require is manufactured [mcve]. So, please provide an example for both cases, so we could tell what is different between them. – Algirdas Preidžius Aug 24 '18 at 11:49
  • @JesperJuhl I perhaps not said it clearly: I do understand that defining is necessary. I was more surprised that sometimes it isn't. That question doesn't answer why/when it isn't – MateuszL Aug 24 '18 at 12:07
  • @AlgirdasPreidžius I know, but to produce such example I would have to understand the difference between two usages of the member by both loggers and probably knew the whole answer by then. – MateuszL Aug 24 '18 at 12:09
  • 4
    @MateuszL That's the whole purpose of [mcve]. Chances are, that you may solve the issue on your own, by the time you are able to produce it. How do you expect us to answer such a question, without having necessary information, such as the differences between the cases? – Algirdas Preidžius Aug 24 '18 at 12:14
  • @AlgirdasPreidžius I hoped that there is some universal C++ rule saying "if conditions A, B, C are met, definition of static data member may/must be omitted". – MateuszL Aug 24 '18 at 12:17
  • 1
    @MateuszL Self-research is not a vice :P – Lightness Races in Orbit Aug 24 '18 at 14:00

2 Answers2

2

Unless the variables are declared inline (a C++17 feature), definitions of static member variables are not optional, as far as the C++ standard is concerned. Failure to provide a definition is undefined behavior.

Compilers and linkers may vary on exactly what will make them check to see if definitions exist, but that is the nature of undefined behavior.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Isn't `constexpr int bar = 10;` another exception, even before C++17? – MSalters Aug 24 '18 at 14:23
  • @MSalters: [Not according to cppreference](https://en.cppreference.com/w/cpp/language/static): "*If a const non-inline static data member or a constexpr static data member is odr-used, a definition at namespace scope is still required*" It's only C++17 that makes `constexpr` static members implicitly `inline` and therefore not require a definition. – Nicol Bolas Aug 24 '18 at 14:37
  • @cpplearner: Well, you *could* remember the various rules of "odr-use". Or you could just remember that non-`inline` static members require definitions. – Nicol Bolas Aug 24 '18 at 18:59
  • But why do you think `inline` variables do not require definitions? – cpplearner Aug 24 '18 at 19:45
  • @cpplearner: I'm not sure I understand the question. `inline` variables don't require definitions because that's *exactly why the feature was invented*: to allow people to write global/class static variables without writing separate definitions for them. – Nicol Bolas Aug 24 '18 at 21:14
  • It seems that you don't think `static inline int bar = 0;` is a definition. OK fine. – cpplearner Aug 25 '18 at 04:13
  • @cpplearner: Or that when I said "separate definitions", I actually meant *separate*. – Nicol Bolas Aug 25 '18 at 04:48
  • Thank you for this answer. I didn't expect it to be UB, so didn't research topics around ODR deep enough. Now I managed to find minimal example that illustrates my problem :) – MateuszL Aug 27 '18 at 09:35
0

As Nicol Bolas answered, the code in my project had undefined behavior because static data members were initialized but nowhere defined. To summarize and extend: Static data member doesn't need to be defined when:

  • It is not used or is used only in discarded branches (non-instantiated templates and discarded branches of constexpr-if)
  • in C++17 if member is inline
  • also clang-tidy says that "out-of-line definition of constexpr static data member is redundant in C++17 and is deprecated", so probably static constexpr also doesn't need it

Further, following code shows why my bad project was not triggering linker error before. I don't know whether it is "not odr-use" or "Undefined Behavior that doesn't hurt you yet":

#include <boost/serialization/version.hpp>
class Klass {};
//BOOST_CLASS_VERSION(Klass, 3);
// would be expanded to:
namespace boost { namespace serialization {
template<>
struct version<Klass> {
    static const int value = 3; // not defined anywhere
};
} }

int foo (int val) { // was used by old logger
    return val;
}
int bar (const int &val) { // is used by new logger
    return val;
}
int main () {
//    return bar(boost::serialization::version<Klass>::value); // link error
    return foo(boost::serialization::version<Klass>::value); // works fine
}

So there is no link error if member is used but not it's address is not queried. Passing value by reference qualifies as querying the address.

MateuszL
  • 2,751
  • 25
  • 38