Search code examples
c++inlineone-definition-rule

Equivalence of static function for methods


Sometimes, I want to have a function in a header file (included in multiple different translation units) without telling the compiler to inline it (for example in a header-only library). This is easy when doing it the C-style way, just declare the function static, e.g.:

   struct somedata { ... }

   static somefunc (somedata *self) { ... }

This lets the compiler decide whether to inline the function or not, while still allowing multiple definitions of the function in multiple translation units, as it has no external linkage. And also allows me to call this function with somedata structs created in other translation units, because the types are compatible.

My question is, how do I do this with classes and methods? For example, take this header file, which is practically the same thing using a class and a method instead of a function and an explicit object pointer:

   struct someclass {
      void method ();
   }

   void someclass::method () { ... }

Obviously, I can't use static someclass::method because that is something else entirely.

I also cannot put this into an anonymous namespace, because then I get different struct someclass types in different translation units, i.e. I couldn't use a someclass * from one file in another, because they would be different (and incompatible) types.

I can declare all these methods inline, which would work, but would have the undesirable effect of, well, asking the compiler to inline them even when that makes no sense.

Am I missing something obvious, or does C++ not have the equivalent of static for methods? As I see it (hoping to be wrong), the only options for me are to either move all these methods into a separate translation unit or mark them all as inline - there seems to be no equivalent of C-style internal linkage.

Update: I think this question was closed prematurely as duplicate. The supposedly duplicate question is about whether marking functions as inline will always inline them. This question is about how to avoid the ODR rule as the static keyword for plain functions does, or explain that this cannot be done in C++, neither of which is answered by the other question, which simply tells the asker to not worry about it. In my question, inline is only mentioned as a possible (but bad) solution.

Update 2: It's been mentioned multiple times that inline is nnot a request for function inlining, or that the C standard only uses inline to get around the ODR rule and does not ask the compiler to inline a function.

Both statements are clearly untrue. For example perusing GCC documentation or LLVM source code reveals that widely used compilers do consider inline as a request to inline a function. I also quote from C++03, which says (in 7.1.2.2):

[...] The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. [...]

So existing compilers and the C++ standard (I only checked 148882:2003) clearly disagree with the repeated claim of "inline only affects ODR".

This wrong perception seems to be quite widespread, as seen e.g. here: https://blog.tartanllama.xyz/inline-hints/ where somebody investigates this claim by looking at actual GCC/LLVM source code and finds that both compilers treat inline as an actual inlining request.

However, keep in mind that my question is about how to get the effect of static for member functions in C++, or alternatively to get a more definitive statement that C++ simply doesn't have this feature for member functions, only for plain functions. The properties of inline are only relevant here to the extent that it does solve the problem at the expense of potentially unwanted inlining, which can be bad for performance.

It's relatively clear to me that there is no way around the one definition rule. What is not clear to me is whether there is really no other way to achieve this effect. For example, the next best thing to static is an anonymous namespace, but that doesn't work either, as it makes the structs declared in it all incompatible between different translation units, so they can't be interchanged.

I hope there might be a way around it, for example, by having the struct outside the anonymous namespace and having a derived class inside, or some other construct, but I can't see how at the moment, while at the same time I cannot rule out that it might be possible - thus this question.

Update 3: To clarify the example - the methods do not contain static variables, and it doesn't matter whether the end result results in multiple physical different copies of a method or not, as long as all such copies behave the same. An actual example of such a method would be:

char *reserve (int bytes)
{
  if (left <= bytes)
    flush ();

  if (left <= bytes)
    throw std::runtime_error ("bulkbuf allocation overflow");

  return cur;
}

If this method is called often (in the source), but not often (at runtime), asking the compiler to inline it for no reason could be detrimental to performance and certainly to code size.

Update 4: There are many repeated claims that compilers universally ignore the inline keyword as a request for inlining, despite good evidence that this is incorrect,.

Just to make get rid of any doubts, I tried it out with this (rather nonsensical) program:

//inline
int f(int i)
{   
  return i < 0 ? 0 : f(i-1) + 1;
} 

int main(int argc, char *[])
{   
  return f(5) + f(argc);
} 

Note the commented out inline keyword.

When I compile this with the g++ 6.3.0 (released 2016) from Debian GNU/Linux Stretch (using g++ -Os -S -o - test.C), I get this main program when inline is commented out:

    movl    %edi, %ecx
    movl    $5, %edi
    call    f(int)
    movl    %eax, %edx
    movl    %ecx, %edi
    call    f(int)
    addl    %edx, %eax
    ret

And this when inline is active in:

        xorl    %eax, %eax
.L3:
        cmpl    %eax, %edi
        js      .L2
        incl    %eax
        jmp     .L3
.L2:
        addl    $6, %eax
        ret

So without inline the function did not get inlined, with inline, it did get inlined. At the very least, this proves that compilers do not universally ignore inline as a request for inlining, as often claimed (and g++ is certainly one of the few major C++ compilers out there, and version 6.3 is hardly obsolete, so this is not a weird niche compiler).

So fact is, both the standard and existing compilers do treat inline as more than just an ODR behaviour change, namely as an explicit request to inline the function.

Note that whether a compiler ignores the hint or not is only tangentially relevant to my question, which is about the C++ language, not any compilers, and at least C++03 requires compilers to "preferentially" inline functions marked as such, without requiring them to do so, so my concerns with inline are valid whether compilers ignore it or not.

Update 5:

Changing f to this:

return i < 0 ? 1 : f(i-1) + f(i-2);

results in the analog behaviour with both clang++ 3.8.1-24 and g++. Also, Do c++11-compatible compilers always ignore inline hints? claims MSVC also heads the inline keyword as request for actual inlining.

g++, clang++/LLVM and MSVC together cover a large share of the C++ "market", so it's safe to say that compilers almost universally treat inline as request for inlining, whether they heed it or not, and in addition to the other requirements from the C++ standard.


Solution

  • Semantics of static and inline for non-member functions is different even if function definitions are otherwise identical.

    // in several translation units
    static void foo_static() { 
       static int bar; // one copy per translation unit
    }
    
    // in several translation units
    inline void foo_inline() { 
       static int bar; // one copy in the entire program
    }
    

    &foo_static will also be different across translation units, while &foo_inline will be the same.

    There's no way to request static semantics for member functions (even for static member functions).

    There is also no way to request inline semantics for any function without actually declaring it (explicitly or implicitly) inline. In other words, there's no way to say "make this function behave like inline in everything except actual inlining".

    On the other hand, semantics for function templates is similar to that of inline functions without a request to the compiler (however meaningless it is nowadays) to inline them at their call sites.

    // in several translation units
    template <nullptr_t=nullptr>
    void foo_template() { 
       static int bar; // one copy in the entire program
    }