0

I've been having some problems with declaring functions inline causing unresolved external reference linker errors. I must be misunderstanding something quirky about C++. I'm trying to reduce the compile time of my C++ SDK using a 3-file translation unit where there is one "codeless header" that has only declarations and no implementations, another "code header" that contains all of the templates with implementations, and a unique .cpp filename to minimize hashtable collisions. I'm trying to make either a statically compiled library, DLL, or compile directly into an executable. I want my functions to be inlined, but the problem is that this super basic code will not compile:

// in pch.h
#include <iostream>
#ifdef ASSEMBLE_DYNAMIC_LIB
#ifdef LIB_EXPORT
#define LIB_MEMBER __declspec(dllexport)
#else
#define LIB_MEMBER __declspec(dllimport)
#endif
#else
#define LIB_MEMBER
#endif

// in foo.h
#pragma once
#include "pch.h"
struct LIB_MEMBER Foo {
   Foo ();
   inline int Bar (); //< inline causes Unresolved external reference error???
};

// in foo.cpp
#include "foo.h"
Foo::Foo () {}
int Foo::Bar()

// main.cpp
#include "foo.h"

int main(int argv, char** args) {
  Foo foo;
  std::cout << "Hello StackOverflow. foo is " << foo.Bar();
  while (1)
    ;
}

The code results in this linker error:

Severity Code Description Project File Line Suppression State Error LNK2019 unresolved external symbol "public: int __cdecl Foo::Bar(void)" (?Bar@Foo@@QEAAHXZ) referenced in function main experiments C:\workspace\kabuki_toolkit\projects\experiments\main.obj 1

All of the code I've found on StackOverflow won't compile with the same error. For example:

// in foo.cpp
#include "foo.h"
Foo::Foo () {}
inline int Foo::Bar() {} //< Again, Unresolved external reference error :-(

The Visual-C++ documetnation has some stuff about how to inline a DLL class member, but they have no code examples.

  • 4
    Exported functions cannot be inline - why do you think this is the case? – Dai Aug 12 '19 at 01:32
  • 2
    The error message you cite doesn't appear to bear any relation to the code you've shown. – Igor Tandetnik Aug 12 '19 at 01:40
  • They all do that. I've got hundreds of functions that do the same error message. – Cale McCollough Aug 12 '19 at 01:42
  • Quote from Microsoft: "You can define as inline a function with the dllexport attribute." – Cale McCollough Aug 12 '19 at 01:44
  • Maybe it's that I have to declare it as not inline int he "codless header" and put the declaration in the "code header" declared inline? – Cale McCollough Aug 12 '19 at 01:49
  • That wouldn't help because I still have to import the symbol inline. It's just one of the left-field C++ things you would think is easy but it turns out to be really old and hacked together. – Cale McCollough Aug 12 '19 at 01:54
  • Do you want the function to exist in the DLL (so that, e.g., it can be replaced if the DLL is updated), or do you want the function to exist in each translation unit where it is used (see [When should I write the keyword 'inline' for a function/method?](https://stackoverflow.com/questions/1759300/when-should-i-write-the-keyword-inline-for-a-function-method)). Having both is not logically inconsistent, but it does make the DLL's copy of the function superfluous. – JaMiT Aug 12 '19 at 03:20
  • The quote you give from Visual C++'s documentation just says that something can be done (it is not a compile-time error). It does not say that the something has any useful purpose. (Not to mention that Microsoft does not have the best track record when it comes to having accurate documentation...) What is it you hope to accomplish with this strange setup? – JaMiT Aug 12 '19 at 03:23
  • Just remove `inline`? – Alan Birtles Aug 12 '19 at 05:43
  • @Dai if the class is defined with `__declspec(dllexport)` it means all of the member functions are exported. (In fact it is not possible to have some member functions of a class exported and some not). – M.M Aug 13 '19 at 04:41
  • In the latest edit `void Foo::Bar() {}` should fail to compile since the function was declared to return `int`. It would be better to copy and paste the code that you are having trouble with, rather than making a series of incremental edits that introduce mistakes – M.M Aug 13 '19 at 05:31
  • The latest edit is even worse. `inline int Bar (); {}` is a syntax error and so is `int Foo::Bar()` (followed by end-of-file) – M.M Aug 13 '19 at 10:31
  • With the technique I've discovered here, I've gotten the compile-time of Script2 down from 10 seconds to about 3 seconds TOTAL. I've also invented a new number of printing algorithm with less than half of the division instructions, and I have also crafted a 3-instruction pointer algihrm algorithm, so it looks like I have struck again! – Cale McCollough Sep 30 '19 at 15:57

4 Answers4

1

In C++ inline functions must have their body present in every translation unit from which they are called, otherwise the program is ill-formed. (Ref: C++17 [basic.def.odr]/4)

Marking the class as dllexport or dllimport does not escape that requirement.

NB. The semantics of inline functions in exported classes are described on this MSDN page ; it's exported the same way as a non-inline function; and the importing compiler can choose whether to use the inline definition or the imported definition.

To fix this you could declare Bar as non-inline; or provide the body in the header, e.g.

struct LIB_MEMBER Foo {
    Foo ();
    void Bar () { } 
};

or, equivalently,

struct LIB_MEMBER Foo {
    Foo ();
    inline void Bar () { } 
};

inline void Foo::Bar() {}

As mentioned by the MSDN page, having an exported class with an inline function means that you can't safely change the function in a later release of the DLL without recompiling the client.

M.M
  • 138,810
  • 21
  • 208
  • 365
0

You've set ASSEMBLE_DYNAMIC_LIB to NO_0 in the header, which means all of the export flags are ignored. Your pre-processor usage is also wrong: You probably don't want to be using #ifdef, and should be using #if when posing a question...

#if ASSEMBLE_DYNAMIC_LIB == YES_0

If you intend for void Foo::Bar() {} to be inline, then move it to the header file. If it's in the cpp, you'll get an unresolved external error (potentially). It is however worth noting that MSVC will ignore inline in this context, given that you have asked to export the entire class (export overrules inline).

It is possible to export individual member functions as well, if you want to mix & match inline/exported.

class Foo {
public:
   LIB_MEMBER Foo ();
   OBJ_INLINE void Bar () 
     {}
};
robthebloke
  • 9,331
  • 9
  • 12
  • Sorry, the macros I was using were debug macros because of the issue. I have deleted them. – Cale McCollough Aug 12 '19 at 01:59
  • The problem is the Microsoft documentation says you can do it. – Cale McCollough Aug 12 '19 at 02:13
  • Yes, you can mix inline & exported (if you export dividual member functions), but if you export the entire class, inline is overruled and all methods will be exported. That's what exporting a class means. – robthebloke Aug 12 '19 at 04:19
  • And, if you stick inline methods in the cpp file, well you are simply asking for a linker error by design. Put the method in the header file. – robthebloke Aug 12 '19 at 04:23
0

Edit: I believe this answer is wrong. Preserved for the comments below.

It is recommended to read the entire documentation page before jumping to coding.

These rules apply to inline functions whose definitions appear within a class definition.

Your function definition does not appear within a class definition, only the declaration does.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I found the answer and it's not what you would think. All C++ class members are actually inline. MIND BLOWN!!! I'm a C++ ninja and this was something I sluffed off. CodeGuru says "Any function defined within the body of a class automatically becomes an inline function. However, if we want a non-class function to be inline, we can do it by using the keyword inline. We must define an inline function as soon as we declare it; otherwise, it will be treated as a normal function declaration." – Cale McCollough Aug 13 '19 at 02:38
  • 1
    @CaleMcCollough "All C++ class members are actually inline" this is incorrect and this is not what the source you quoted is saying. – n. m. could be an AI Aug 13 '19 at 02:54
  • This is subtle, in section 9.3(3) of the C++14 standard reads "There shall be at most one definition of a non-inline member function in a program; no diagnostic is required. There may be more than one inline member function definition in a program." It's not saying that it will inline your member, but it's saying that only one exists, and the compiler then implies to use the one that it finds fastest. Given it's a DLL, there is only copy of the inline member so the compiler will reduce the ROM ssize with the optimizer while linking. – Cale McCollough Aug 13 '19 at 03:46
  • Now the paper about removing inline from the C++ standard makes sense. When you declare a function inline in C++, it basically means that there is more than one copy of that function so the compiler needs to search through them for the best one. In the case the DLL, this construct doesn't do anything because the compiler put a copy of it in the DLL. At that point, you're at the mercy of the optimizer that is looking to inline whatever it can whether you marked it inline or not. – Cale McCollough Aug 13 '19 at 03:53
  • I've changed the question slightly and half-taken out the DLL part. – Cale McCollough Aug 13 '19 at 06:04
  • I think this answer is incorrect and/or irrelevant, I will preserve it for comments. – n. m. could be an AI Aug 13 '19 at 09:39
-1

foo.cpp won't compile because of the C++ standard (C++14 in this case) Section 9.3 states:

  1. An inline member function (whether static or non-static) may also be defined outside of its class definition provided either its declaration in the class definition or its definition outside of the class definition declares the function as inline. [ Note: Member functions of a class in namespace scope have external linkage. Member functions of a local class (9.8) have no linkage. See 3.5. —end note ]

  2. There shall be at most one definition of a non-inline member function in a program; no diagnostic is required. There may be more than one inline member function definition in a program. See 3.2 and 7.1.2.

Also Section 3.4 also states:

An inline function shall be defined in every translation unit in which it is odr-used. Exactly one definition of a class is required in a translation unit if the class is used in a way that requires the class type to be complete.

The keyword to understand here is external linkage; If there is more than one definition of the symbol at link time then it can't be resolved because of the one definition rule1; all C++ class members defined in namespace scope have external linkage and there are multiple definitions of an inline symbol, one for each translation unit, so the external symbol can't be resolved; hence the compile error Unresolved External Symbol. When we put the definition of the declaration outside of the class and mark it inline, it's no different than putting the inline code in the header. So if we move the code from foo.cpp into main.cpp, it works because there is only one TU now and no violates of the one definition rule:

#include "foo.h"

Foo::Foo() {}

inline int
Foo::Bar() {  //< No undefined external refrence error.
  return 1;
}

int main(int argv, char** args) {
  Foo foo;
  std::cout << "Hello StackOverflow. foo is " << foo.Bar();
  while (1)
    ;
}

The error in logic of using inline in the declarations for the statically linked library is that we assume that the compiler will not inline the functions automatically using the optimizer. Compilers perform Link-Time Code Generation, or Whole Program Optimisation, where the linker will delay generating the code until link-time and will optimize the intermediate code without the use of a single inline tag2. The problem with using a 3-file translation unit with a DLL is that you're linking at runtime and the intermediate code got deleted so you can't use the Link-time Code Generation to inline the function, so you'll have to put inline functions in the "code header" with the templates.

Updated 9/14/2019 Answer

I have found a significantly more eloquent solution, and you can see an example in the Script2 SDK. The solution is that you only really want one translation unit. I just got 900KB of Script2 code to compile in about 4 seconds total, with 3 seconds spent on pch.cc. The trick is to rename your .cpp files to .inl files, which are inline header files, and include then all in another inline header, Script2 using the module_assembly.inl file. This will prevent you from having to pre-compile a statically linked library and lose the intermediate object code you need for the compiler to auto-inline your functions during the Whole program optimization phase. The new naming convention is that the "codeless header" is a .h file, the "code header" is a .hpp file, and the inline cpp file is .cc.inl file.

  • Your code sample should fail to compile since `Bar` was declared to return `void` – M.M Aug 13 '19 at 04:48
  • that was a typo. If the c++ class member is marked inline it says it doesn't have external linkage, so you when you mark it inline the "external reference" is undefined. – Cale McCollough Aug 13 '19 at 05:24
  • It's not appropriate to make a claim like "all of the other Stackoverflow Q&A on inline C++ class members are missing a critical piece of information". Instead you could perhaps highlight a specific answer that has a problem. Most of your last 2 paragraphs is nonsense: a function has a single address , whether `inline` or not. All class member functions have external linkage, `inline` or not. "assembly line boundary" is not standard terminology and it's not clear what you mean by that. Also "you" don't inline a function, the compiler does. – M.M Aug 13 '19 at 05:37
  • It is appropriate, most people are not aware of assembly line boundaries and their code doesn't compile. Only working in the main.cpp is not an acceptable answer. Can you please explain to me why it works in the main.cpp but not in foo.cpp? – Cale McCollough Aug 13 '19 at 05:44
  • If you clearly post the actual code you want explained I'll explain it. "assembly line boundary" is not part of the specification of C++, it seems to be some term you've made up but won't let anyone else in on what you mean by it. – M.M Aug 13 '19 at 10:33
  • An assembly line boundary is not part of the C++ spec because it's a fundamental assembly language construct. Your answer is wrong because you claim there is undefined behavior when in reality the inline function gets compiled into each translation unit and can't be resolved because it violates the one definition rule. If an inline function gets compiled into each translation unit, you can't give it external linkage because that means it has a single reference to link too. It's very similar in this case to how a static funciton in C gets compiled into each TU. – Cale McCollough Aug 14 '19 at 02:16
  • Examples of an assembly line boundary are a statically compiled library, DLL, or binary executable. A translation unit is still in intermediate code because it hasn't been linked yet, but it's functionally identical. Basically, you have a binary file with a symbol table that maps the function calls in the header to the actual function calls at specific addresses. This is called external linkage. You can use a diagnostics tool to inspect the binary and find more than one copy of a function though, hence the C++ spec wording, but that's not extrenal linkage, that's a hack. – Cale McCollough Aug 14 '19 at 02:36
  • Static libraries are just a collection of object files , storing some objects in a library file before linking is exactly the same result as linking with those objects in the first place – M.M Aug 14 '19 at 03:19
  • Re. your second to last comment, there's undefined behaviour because the standard says there is. The standard sets out the rules for the C++ langauge. It also says that inline class member functions have external linkage. You seem to be considering your own ideas to be correct while the standard is wrong. Which raises the question of why you posted this in the first place if your intent was just to ignore any responses. – M.M Aug 14 '19 at 03:21
  • Where does the C++ standard say there is undefined behavior? I cited the spec, it doesn't contain that language. The spec says " [ Note: Member functions of a class in namespace scope have external linkage. Member functions of a local class (9.8) have no linkage. See 3.5. —end note ]" – Cale McCollough Aug 14 '19 at 05:11
  • See C++17 [basic.def.odr]/4 "An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement" – M.M Aug 14 '19 at 05:20
  • I updated my answer, this condition is actually ill-formed, not UB-NDR – M.M Aug 14 '19 at 05:31