1

I'm trying to refactor some classes from a large main file into separate header and cpp files and am getting undefined reference errors at link time.

I've got a project that looks like this:

├── CMakeLists.txt
├── data
│   └── ICING BE SI Data.csv
├── gcc
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── lib
│   ├── Makefile
│   ├── src
│   └── tmp
├── include
│   ├── Interpolator.hpp
│   ├── InverseCDFProcess.hpp
│   └── XYParser.hpp
├── lib
│   ├── CMakeLists.txt
│   ├── Interpolator.cpp
│   ├── InverseCDFProcess.cpp
│   └── XYParser.cpp
└── test
    └── test_icing.cpp

The project has a few classes, Interpolator and InverseCDFProcess, which I recently moved from the main executable file, test_icing.cpp to their own .cpp and .hpp files, located within the lib and include directories, respectively.

Since the classes do depend on each other (InverseCDFProcess needs Interpolator, which in turn needs a function in XYParser.cpp), I decided to build them as static libraries that then get linked into the main executable at compile time.

They're built like so:

add_library(xyparser STATIC XYParser.cpp)
add_library(interpolator STATIC Interpolator.cpp)
add_library(inversecdf STATIC InverseCDFProcess.cpp)

I then link these libraries into my executable in the normal way:

include_directories(include)
link_directories(lib)
link_directories(include) # Do I need this?

add_executable(test_icing test/test_icing.cpp)

# ... some code adding an external library which works fine

target_link_libraries(test_icing inversecdf interpolator xyparser ${external_library_name})

This produces this link command:

/usr/bin/c++ CMakeFiles/test_icing.dir/test/test_icing.cpp.o -o test_icing -L/mnt/c/Users/foo/projects/chase-icing/lib -L/mnt/c/Users/foo/projects/chase-icing/include -L/mnt/c/Users/foo/projects/chas e-icing/gcc/src/imtc-build/lib -Wl,-rpath,/mnt/c/Users/foo/projects/chase-icing/lib:/mnt/c/Users/foo/projects/chase-icing/include:/mnt/c/Users/foo/projects/chase-icing/gcc/src/imtc-build/lib lib/libinversecdf .a lib/libinterpolator.a lib/libxyparser.a -limt

At this point the compilation stop with the error:

/mnt/c/Users/foo/projects/chase-icing/test/test_icing.cpp:(.text+0xcca): undefined reference to `Interpolator<double>::Interpolator(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > co
nst&)'
/mnt/c/Users/foo/projects/chase-icing/test/test_icing.cpp:(.text+0xd4c): undefined reference to `Interpolator<double>::set_bounds(std::pair<double, double> const&)'
/mnt/c/Users/foo/projects/chase-icing/test/test_icing.cpp:(.text+0xd99): undefined reference to `InverseCDFProcess<double>::InverseCDFProcess(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<
char> > const&)'
/mnt/c/Users/foo/projects/chase-icing/test/test_icing.cpp:(.text+0xdd9): undefined reference to `InverseCDFProcess<double>::generate()'

It doesn't matter if the libraries are built STATIC or SHARED. The undefined reference error still happens.

My question is this: am I missing some extern or similar in my class definitions or implementations? Why is this relatively straightforward refactoring resulting in undefined references? Is my link directory incorrect? Should it refer to build directories?

Any help is appreciated.

ijustlovemath
  • 703
  • 10
  • 21
  • Where is ` Interpolator::Interpolator(...)` defined? – tuket Apr 11 '20 at 19:51
  • 1
    So are the missing functions in the header file or the cpp file? It seems you may not realise that [all template code should be placed in header files](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file). – john Apr 11 '20 at 19:51
  • 1
    @john Not necessarily. You could use explicit template instantiation in the cpp – tuket Apr 11 '20 at 19:53
  • Or, to put it another way, you cannot make a static library out of a template. – john Apr 11 '20 at 19:53
  • 1
    @tuket Yes that's true, but a rare use case, and I try not to confuse newbies with it. It is covered in the link I provided. The important point is that template code is not like regular code. Something which surprises everyone the first time they find out . – john Apr 11 '20 at 19:54
  • So the templated classes should be purely in the header files?? That makes sense I suppose, didn't know they were always done at compile time – ijustlovemath Apr 11 '20 at 19:59
  • All the functions are defined in .cpp files, but they're templates – ijustlovemath Apr 11 '20 at 19:59
  • @john that was the solution, thank you so much! – ijustlovemath Apr 11 '20 at 20:04
  • 1
    @ijustlovemath 'header only libraries' is the phrase often used. – john Apr 11 '20 at 20:24
  • I always figured that meant you had the *option*, not that it was a *requirement* (excluding the `#include a.tpp` stuff in the link you mentioned). Good to know going forward! – ijustlovemath Apr 11 '20 at 20:31
  • "My question is this: am I missing some extern or similar in my class definitions or implementations?" - Most likely you have missed something, but since you don't show the code we cannot be sure. Please, do not *describe* the code you have - **show** the instead (add it to the question post). – Tsyvarev Apr 11 '20 at 22:10
  • @Tsyvarev, the problem was that I had implemented the templates in a .cpp file, and merely declared them in the .hpp, which as john pointed out was a mistake – ijustlovemath Apr 11 '20 at 22:20

1 Answers1

1

Gonna go ahead and answer this for future people:

The solution is embedded in the error messages:

/mnt/c/Users/foo/projects/chase-icing/test/test_icing.cpp:(.text+0xdd9): undefined reference to `InverseCDFProcess<double>::generate()'

This error shows that the classes are templated. The problem is that I placed the implementations of these templates in .cpp files, as seen here:

├── include
│   ├── Interpolator.hpp
│   ├── InverseCDFProcess.hpp
│   └── XYParser.hpp
├── lib
│   ├── CMakeLists.txt
│   ├── Interpolator.cpp
│   ├── InverseCDFProcess.cpp
│   └── XYParser.cpp

Templates need to contain the full implementation in the header files. So, the good news is that I don't need those libraries in the first place. Just #include "Interpolator.hpp etc and it should work as expected!

The reason templates need the implementation is seen here: Why can templates only be implemented in the header file?

ijustlovemath
  • 703
  • 10
  • 21