2

Several times I've seen code like this in a header

IMPORTOREXPORT std::string const& foo();
IMPORTOREXPORT std::string const& bar();
IMPORTOREXPORT std::string const& baz();

and the following corresponding code in a cpp file

std::string const& foo() {
  static std::string const s{"foo"};
  return s;
}
std::string const& bar() {
  static std::string const s{"bar"};
  return s;
}
std::string const& baz() {
  static std::string const s{"baz"};
  return s;
}

where IMPORTOREXPORT is a macro to deal with linkage, so yes, I'm talking of a multi-module code, where different translation units are compiled independently and then linked together.

Is there any reason why I should prefer the above solution to simply have these in the header?

inline std::string const foo = "foo";
inline std::string const bar = "bar";
inline std::string const baz = "baz";

I found this related question, but I'm not sure it entirely answers this. For instance, the accepted answer contains this (part of a) statement

for different modules would be different addresses

but this doesn't seem to be inline with what read on cppreference (emphasis mine):

  1. An inline function or variable (since C++17) with external linkage (e.g. not declared static) has the following additional properties:

    1. It has the same address in every translation unit.
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • As you stated in your question, inline variables were introduced in C++17. So could this be old code or an attempt of supporting old compilers or an old habit? In addition, the behavior is a bit different, because in one variant the initialization of the variable is postponed to the first function call. The pattern reminds me of the [Meyer's singleton](https://laristra.github.io/flecsi/src/developer-guide/patterns/meyers_singleton.html), though using it with `std::string` defends (some of) its purpose IMHO. – joergbrech Nov 21 '22 at 22:10

1 Answers1

1

As you note, the inline variable approach guarantees that the variable has a unique address, just like the static local variable approach.

The inline approach is certainly easier to write, but:

  1. It doesn't work on pre-C++17 compilers.
  2. It's more expensive to build:
    1. Every translation unit that odr-uses the inline variable must emit a weak definition for that variable, and the linker must discard all but one of these symbols so that the variable has a unique address.
    2. If the variable has dynamic initialization, then every translation unit that odr-uses it also needs to emit a piece of code to dynamically initialize it, and again, the linker must discard all but one copy.
    3. Every translation unit that includes the header that defines the inline variable is forced to have a compile-time dependency on the header that defines the variable's type, even if there are no uses of the variable besides the definition itself.
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • As regards point 2.3, are you referring to the fact that with the inline approach the header uses `std::string`, so a complete definition is needed, whereas with the other approach the header only uses `std::string const&` for which a forward declaration would be enough? – Enlico Nov 22 '22 at 08:30
  • 1
    @Enlico Yes, exactly. Well, you shouldn't forward-declare std::string, but this applies to user-defined class types. – Brian Bi Nov 22 '22 at 14:51
  • Yeah, that's clear thanks. I wonder if the fact that the compiled main is bigger with [the inlne approach](https://godbolt.org/z/f7151q7a7) than with [the header + impl approach](https://godbolt.org/z/3xvbcr6oT) is already a symptom of what you describe in such a simple case as the one at the two links. – Enlico Nov 22 '22 at 18:52