Consider the header file:
class T
{
private:
int const ID;
public:
explicit T(int const ID_) noexcept : ID(ID_) {}
int GetID() const noexcept { return ID; }
};
or, alternatively:
class T
{
private:
int const ID;
public:
explicit T(int const ID_) noexcept;
int GetID() const noexcept;
};
inline T::T(int const ID_) noexcept : ID(ID_) {}
inline int T::GetID() const noexcept { return ID; }
In a pre-modules world, these headers might be textually included in multiple TU without ODR violations. Furthermore, since the involved member functions are relatively small, the compiler would likely "inline" (avoid function calls when using) those functions, or even optimize away some instances of T
altogether.
In a recent report on the meeting where C++20 was finished, I could read the following statement:
We clarified the meaning of
inline
in module interfaces: the intent is that bodies of functions that are not explicitly declaredinline
are not part of the ABI of a module, even if those function bodies appear in the module interface. In order to give module authors more control over their ABI, member functions defined in class bodies in module interfaces are no longer implicitlyinline
.
I'm not sure I'm not mistaken that. Does that mean that, in a modules world, for the compiler to be able to optimize away function calls we have to annotate them as inline
even if they are defined in-class?
If so, would the following module interface be equivalent to the headers above?
export module M;
export
class T
{
private:
int const ID;
public:
inline explicit T(int const ID_) noexcept : ID(ID_) {}
inline int GetID() const noexcept { return ID; }
};
Even though I still don't have a compiler with modules support, I would like to start using inline
like so when appropriate, to minimize future refactoring.
Does that mean that, in a modules world, for the compiler to be able to optimize away function calls we have to annotate them as
inline
even if they are defined in-class?
To some degree.
Inlining is an "as if" optimization, and inlining can happen even between translation units if the compiler is clever enough.
That being said, inlining is easiest when working within a single translation unit. Thus, to promote easy inlining, an inline
-declared function has to have its definition provided in any translation unit where it is used. This doesn't mean the compiler will certainly inline it (or certainly not inline any non-inline
-qualified function), but it does make things a lot easier on the inlining process, since the inlining is happening within a TU rather than between them.
Class member definitions defined within a class, in a pre-module world, are declared inline
implicitly. Why? Because the definition is within the class. In a pre-module world, class definitions that are shared among TUs are shared by textual inclusion. Members defined in a class would therefore be defined in the header shared among those TUs. So if multiple TUs use the same class, those multiple TUs are doing so by including the class definition and the definition of its members declared in the header.
That is, you're including the definition anyway, so why not make it inline
?
Of course, this means that the definition of a function is now a part of the class's text. If you change the definition of a member declared in a header, this forces the recompilation of every file that includes that header, recursively. Even if the class's interface itself isn't changing, you still need to do a recompile. So making such functions implicitly inline
doesn't change this, so you may as well do that.
To avoid this in a pre-module world, you can simply define the member in the C++ file, which won't get included in other files. You lose easy inlining, but you gain compile-time.
But here's the thing: this is an artifact of using textual inclusion as a means to deliver a class to multiple places.
In a modular world, you would probably want to define each member function within the class itself, as we see in other languages like Java, C#, Python, and the like. This keeps code locality reasonable, and it prevents having to re-type the same function signature, thus serving the needs of DRY.
But if all members are defined within the class definition, then under the old rules, all of those members would be inline
. And in order for a module to allow a function to be inline
, the binary module artifact would have to include the definition of those functions. Which means that any time you change even one line of code in such a function definition, the module will have to be built, along with every module depending on it, recursively.
Removing implicit-inline
in modules gives users the same powers they had in the textual inclusion days, without having to move the definition out of the class. You can pick which function definitions are part of the module and which ones are not.
This comes from P1779, just adopted in Prague a few days ago. From the proposal:
This paper proposes removing the implicit inline status from functions defined in a class definition attached to a (named) module. This allows classes to benefit from avoiding redundant declarations, maintaining the flexibility offered to module authors in declaring functions with or without inline. Moreover, it allows injected friends of class templates (which cannot be generically defined outside the class definition) to be non-inline at all. It also resolves NB comment US90.
The paper (among other things) removed the sentence:
A function defined within a class definition is an inline function.
and added the sentence:
In the global module, a function defined within a class definition is implicitly inline ([class.mfct], [class.friend]).
Your example with export module M
would be the modular equivalent of the initial program. Note that compilers already do inline functions that aren't annotated inline
, it's just that they additionally use the presence of the inline
keyword in their heuristics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With