0

I'm trying to start a project where I need to implement some genetic algorithms. I am just starting out and trying to figure out a good project structure and how to make the builds easier with cmake. I am also trying to test things as I go along, so I took inpiration from this repository https://github.com/kigster/cmake-project-template.

My problem is that I cannot seem to link a class that I already wrote (genetic_solution) to the tests (I'm using gtest).

I now have the following project structure:

├── bin
├── build
├── CMakeLists.txt
├── include
├── lib
├── src
│   ├── CMakeLists.txt
│   ├── genetic
│   │   ├── CMakeLists.txt
│   │   ├── genetic_solution.cpp
│   │   └── genetic_solution.hpp
│   └── main.cpp
└── tests
    ├── CMakeLists.txt
    └── genetic
        └── genetic_solution_test.cpp

genetic_solution.cpp and genetic_solution.hpp define a genetic_solution templated class. For now, I am just trying to use gtest to test the constructer, the getters and setters. The class is very simple, I'll leave the genetic_solution.hpp here:

#ifndef __GENETIC_SOLUTION_HPP__
#define __GENETIC_SOLUTION_HPP__ 

#include <vector>
#include <iostream>

namespace genetic {
    
    template<typename T>
    class genetic_solution {

        private:
            float _total_cost;
            float _fitness;
            std::vector<float> _costs;
            T _item;

        public:
            genetic_solution(const T item);

            //getters
            float total_cost() const;
            float fitness() const;
            const std::vector<float>& costs() const;
            const T& item() const;

            //setters
            void total_cost(float new_total);
            void fitness(float new_fitness);
            void costs(const std::vector<float>& new_costs);
            void item(const T& new_item);

            virtual void dump_to(std::ostream& os) const = 0;
        
    };

    template<typename T>
    std::ostream& operator<<(std::ostream& os, const genetic_solution<T>& a); 

} // namespace genetic

#endif

My genetic_solution_test.cpp looks like this:

#include <genetic_solution.hpp>
#include <gtest/gtest.h>

class numeric_solution: public genetic::genetic_solution<int> {

    public:
        numeric_solution(int item): genetic::genetic_solution<int>(item) {

        }

        void dump_to(std::ostream& os) const override {
            os << "Solution: " << item();
        }

}; 

class genetic_solution_test: public testing::Test {  

    protected:
        numeric_solution _solution = numeric_solution(10);

};

TEST_F(genetic_solution_test, genetic_solution_constructor_test) {
    ASSERT_FLOAT_EQ(_solution.total_cost(), -1);
    ASSERT_FLOAT_EQ(_solution.fitness(),    -1);
    ASSERT_EQ(_solution.item(), 10);
    ASSERT_EQ(_solution.costs(), std::vector<float>());
}

The ./CMakeLists.txt file looks like this:

cmake_minimum_required(VERSION 3.16.3)

project(bus_network_optimization LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")

set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})

set(GENETIC_INSTALL_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set(GENETIC_INSTALL_BIN_DIR ${PROJECT_SOURCE_DIR}/bin)
set(GENETIC_INSTALL_LIB_DIR ${PROJECT_SOURCE_DIR}/lib)

set(GENETIC_HEADERS_DIR ${PROJECT_SOURCE_DIR}/src/genetic)

include_directories(${GENETIC_INSTALL_INCLUDE_DIR})
include_directories(${GENETIC_HEADERS_DIR})

add_subdirectory(src)
add_subdirectory(tests)

The ./src/CMakeLists.txt file looks like this:

cmake_minimum_required(VERSION 3.16.3)
project(main LANGUAGES CXX)

add_subdirectory(genetic)

add_executable(main main.cpp)
target_link_libraries(main genetic)
install(TARGETS main DESTINATION ${GENETIC_INSTALL_BIN_DIR})

The ./src/genetic/CMakeLists.txt file looks like this:

cmake_minimum_required(VERSION 3.16.3)
project(genetic LANGUAGES CXX)

add_library(genetic SHARED STATIC 
    genetic_solution.hpp
    genetic_solution.cpp)

install(TARGETS genetic DESTINATION ${GENETIC_INSTALL_LIB_DIR})
install(FILES genetic_solution.hpp DESTINATION ${GENETIC_INSTALL_INCLUDE_DIR})

And finally, ./tests/CMakeLists.txt looks like this:

cmake_minimum_required(VERSION 3.16.3)
project(main_tests LANGUAGES C CXX)

include_directories(${GENETIC_HEADERS_DIR})
find_package(GTest REQUIRED)

add_executable(main_tests genetic/genetic_solution_test.cpp)
target_link_libraries(main_tests genetic GTest::Main)
install(TARGETS main_tests DESTINATION bin)

If I go to the ./build directory and run: cmake .. && make && make install I get the following output:

-- The CXX compiler identification is GNU 9.3.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- The C compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Found GTest: /usr/lib/x86_64-linux-gnu/libgtest.a  
-- Configuring done
-- Generating done
-- Build files have been written to: /home/vasco/Documents/Thesis/bus_network_optimization/build
Scanning dependencies of target genetic
[ 16%] Building CXX object src/genetic/CMakeFiles/genetic.dir/genetic_solution.cpp.o
[ 33%] Linking CXX static library libgenetic.a
[ 33%] Built target genetic
Scanning dependencies of target main
[ 50%] Building CXX object src/CMakeFiles/main.dir/main.cpp.o
[ 66%] Linking CXX executable main
[ 66%] Built target main
Scanning dependencies of target main_tests
[ 83%] Building CXX object tests/CMakeFiles/main_tests.dir/genetic/genetic_solution_test.cpp.o
[100%] Linking CXX executable main_tests
/usr/bin/ld: CMakeFiles/main_tests.dir/genetic/genetic_solution_test.cpp.o: in function `genetic_solution_test_genetic_solution_constructor_test_Test::TestBody()':
genetic_solution_test.cpp:(.text+0x33): undefined reference to `genetic::genetic_solution<int>::total_cost() const'
/usr/bin/ld: genetic_solution_test.cpp:(.text+0x88): undefined reference to `genetic::genetic_solution<int>::fitness() const'
/usr/bin/ld: genetic_solution_test.cpp:(.text+0xe5): undefined reference to `genetic::genetic_solution<int>::item() const'
/usr/bin/ld: genetic_solution_test.cpp:(.text+0x1a8): undefined reference to `genetic::genetic_solution<int>::costs() const'
/usr/bin/ld: CMakeFiles/main_tests.dir/genetic/genetic_solution_test.cpp.o: in function `numeric_solution::dump_to(std::ostream&) const':
genetic_solution_test.cpp:(.text._ZNK16numeric_solution7dump_toERSo[_ZNK16numeric_solution7dump_toERSo]+0x29): undefined reference to `genetic::genetic_solution<int>::item() const'
/usr/bin/ld: CMakeFiles/main_tests.dir/genetic/genetic_solution_test.cpp.o: in function `testing::internal::TestFactoryImpl<genetic_solution_test_genetic_solution_constructor_test_Test>::CreateTest()':
genetic_solution_test.cpp:(.text._ZN7testing8internal15TestFactoryImplI60genetic_solution_test_genetic_solution_constructor_test_TestE10CreateTestEv[_ZN7testing8internal15TestFactoryImplI60genetic_solution_test_genetic_solution_constructor_test_TestE10CreateTestEv]+0x36): undefined reference to `genetic::genetic_solution<int>::genetic_solution(int)'
collect2: error: ld returned 1 exit status
make[2]: *** [tests/CMakeFiles/main_tests.dir/build.make:87: tests/main_tests] Error 1
make[1]: *** [CMakeFiles/Makefile2:189: tests/CMakeFiles/main_tests.dir/all] Error 2
make: *** [Makefile:130: all] Error 2

I am using Ubuntu 20.04, cmake 3.16.3 and I got gtest installed with the package libgtest-dev.

I already read several answers to related questions but i could not find the problem. I would really appreciate some help solving this. Can I also ask for tips on testing templated classes?

Thank you very much!

  • 1
    Not sure if it will fix your problem, but isn't `project` a top level attribute? I don't think each `CMakeLists.txt` should have its own project just the one at the top level. – Jack Hughes Mar 22 '21 at 13:48
  • @JackHughes, thank you for your comment! In my particular case it does not make sense to have nested projects. You can have sub-projects, however. Nevertheless, I removed it because it makes no sense, I still cannot link my library to the tests. – Blue Ranger Mar 22 '21 at 14:07
  • You didn't show the file `genetic_solution.cpp`, which should implement your **template** class `genetic_solution`, declared in the `genetic_solution.hpp`, but most likely you don't do that properly. See the [duplicate question](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) about implementing templates. – Tsyvarev Mar 22 '21 at 14:12
  • @Tsyvarev, thank you so much for your comment. I didn't include my class implementation because I was so covinced it was a cmake problem, I didn't even thought of that. I don't have much experience with templates, it was exacly that! It is solved. I just added `template class genetic::genetic_solution;` at the end of my implementation and it is working now. Thank you! – Blue Ranger Mar 22 '21 at 14:42

0 Answers0