0

To start, I'll state that I am quite new to CMake and complex C++ projects. I tried to compile some classes into a static library, and one of these classes (render::ViewRenderer, declared in view.hpp, defined in view.cpp) doesn't have any symbols in the resulting librender.a file. I'm using clang-15 to compile it, but switching to different compilers didn't do anything.

The class render::WindowRenderer (window.hpp, window.cpp) actually compiles without issue. To see if it was an issue with the library compilation, i separated render::ViewRenderer to a separate library -- again render::WindowRenderer was fine, but there were no symbols for render::ViewRenderer. I even tried to compile the sources directly in the executable instead of the library, but still no effect.

One thing that did have effect though (obviously) was including the definition in the view.hpp file, so that nothing needs to be compiled, but that's not desired.

So I gather the problem must lie somewhere in compiling view.cpp.

Here's the code:

view.hpp

#ifndef VIEW_HPP
#define VIEW_HPP

#include <string>


namespace render {
    enum View {
        main_menu,
        game
    };
    /**
     * A class handling the rendering of a window's contents.
    */
    template <class View>
    class ViewRenderer {
        const View view;

    public:
        void render();

        ViewRenderer(View view);
        ~ViewRenderer();
    };
}

#endif

view.cpp

#include "render/view.hpp"


template <class View>
void render::ViewRenderer<View>::render() {

}


template <class View>
render::ViewRenderer<View>::ViewRenderer(View view) : view{ view } {

}

template <class View>
render::ViewRenderer<View>::~ViewRenderer() {

}

window.hpp

#ifndef WINDOW_HPP
#define WINDOW_HPP

#include <GLFW/glfw3.h>
#include <string>


namespace render {
    /**
     * A class to manage the application's windows. A window opens on init and blocks the thread with its execute method.
     * @param name the title of the window to open
     * @param game_renderer the initial GameRenderer to handle the window's contents or nullptr for default behaviour
    */
    enum WindowMode { windowed, fullscreen, windowed_fullscreen };
    class WindowRenderer {
        const char* window_name;
        GLFWwindow* window;
        render::WindowMode window_mode;

        void execute();
    public:
        void (*set_fullscreen)(WindowMode);
        void (*end)();

        WindowRenderer(std::string);
        ~WindowRenderer();
    };
}

#endif

window.cpp

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <string>
#include <stdexcept>

#include "render/window.hpp"

render::WindowRenderer::WindowRenderer(std::string _window_name) : window_name{ _window_name.c_str() }, window_mode{ render::fullscreen } {
    bool glewExperimental = true;
    if (!glfwInit()) {
        throw std::runtime_error("Failed to initialize GLFW\n"); return;
    }

    GLFWmonitor* monitor = glfwGetPrimaryMonitor();
    this->window = glfwCreateWindow(640, 480, this->window_name, NULL, NULL);
    glfwMakeContextCurrent(window);

    if (glewInit() != GLEW_OK) {
        throw std::runtime_error("Failed to initialize GLEW\n"); return;
    }

    this->execute();
}

void render::WindowRenderer::execute() {
    do {
        glClear(GL_COLOR_BUFFER_BIT);

        glfwSwapBuffers(window);
        glfwPollEvents();
    } while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0);
}
render::WindowRenderer::~WindowRenderer() {

}

CMakeLists.txt

set(CMAKE_C_COMPILER /opt/local/bin/clang-mp-15)
set(CMAKE_CXX_COMPILER /opt/local/bin/clang++-mp-15)


cmake_policy(SET CMP0048 NEW)
cmake_minimum_required(VERSION 3.20)

if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(OPENGL_LIBRARY
    ${OPENGL_LIBRARY}
    -lGL -lGLU -lXrandr -lXext -lX11 -lrt
    ${CMAKE_DL_LIBS}
    ${GLFW_LIBRARIES}
)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(OPENGL_LIBRARY
    ${OPENGL_LIBRARY}
    ${CMAKE_DL_LIBS}
    ${GLFW_LIBRARIES}
)
endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")

add_definitions(
    -DTW_STATIC
    -DTW_NO_LIB_PRAGMA
    -DTW_NO_DIRECT3D
    -DGLEW_STATIC
    -D_CRT_SECURE_NO_WARNINGS
)


project(
    game
    VERSION 0.0.1
    LANGUAGES CXX C OBJC OBJCXX
)



include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++20" COMPILER_SUPPORTS_CXX20)
if(COMPILER_SUPPORTS_CXX20)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
else()
        message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++20 support. Please use a different C++ compiler.")
endif()

# ---- Libraries ----


set(SOURCE
    ${CMAKE_SOURCE_DIR}/src/app/render/window.cpp    
    ${CMAKE_SOURCE_DIR}/src/app/include/render/window.hpp    
    ${CMAKE_SOURCE_DIR}/src/app/render/view.cpp    
    ${CMAKE_SOURCE_DIR}/src/app/include/render/view.hpp    
)



# ---- External libraries ----
set(GLEW_VERSION 2.1.0)
set (GLFW_VERSION 3.3.8)
set(GLM_VERSION 0.9.9.8)
# >>>>>>>>>>>>>>>>>>>>>>> GLEW
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/glew-${GLEW_VERSION}/build/cmake) #this produces glew target
set(GLEW_INCLUDE ${CMAKE_SOURCE_DIR}/lib/glew-${GLEW_VERSION}/include)
# >>>>>>>>>>>>>>>>>>>>>>> GLFW
add_subdirectory(lib/glfw-${GLFW_VERSION})
SET(GLFW_INCLUDE lib/glfw-${GLFW_VERSION}/include)
# >>>>>>>>>>>>>>>>>>>>>>>> GLM
SET(GLM_INCLUDE lib/glm-${GLM_VERSION})
# -----------------------------


include_directories(
    ${CMAKE_SOURCE_DIR}/src/app/include
    ${GLEW_INCLUDE}
    ${GLFW_INCLUDE}
    ${GLM_INCLUDE}
)


add_executable(
    game_exe
    ${CMAKE_SOURCE_DIR}/src/app/main.cpp
)
set(TARGET game_exe PROPERTY CMAKE_CXX_STANDARD 20)

add_library(
    render 
    ${CMAKE_SOURCE_DIR}/src/app/render/window.cpp
    ${CMAKE_SOURCE_DIR}/src/app/include/render/window.hpp
    ${CMAKE_SOURCE_DIR}/src/app/render/view.cpp
    ${CMAKE_SOURCE_DIR}/src/app/include/render/view.hpp
)

target_link_libraries(
    game_exe
    render
    glfw
    glew
)

Project structure:

app
    ...
    src
        include
            render
                view.hpp
                window.hpp
        render
            view.cpp
            window.cpp
        main.cpp
    ...
CMakeLists.txt

Build error I am dealing with:

[main] Building folder: game 
[build] Starting build
[proc] Executing command: /opt/homebrew/bin/cmake --build /Users/matt/projects/game/build --config Debug --target all --
[build] [3/5  20% :: 0.445] Building CXX object CMakeFiles/render.dir/src/app/render/view.cpp.o
[build] [3/5  40% :: 0.447] Building CXX object CMakeFiles/render.dir/src/app/render/window.cpp.o
[build] [4/5  60% :: 0.448] Building CXX object CMakeFiles/game_exe.dir/src/app/main.cpp.o
[build] [4/5  80% :: 0.474] Linking CXX static library librender.a
[build] [5/5 100% :: 0.619] Linking CXX executable game_exe
[build] FAILED: game_exe 
[build] : && /opt/local/bin/clang++-mp-15 -std=c++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.0.sdk -Wl,-search_paths_first -Wl,-headerpad_max_install_names  CMakeFiles/game_exe.dir/src/app/main.cpp.o -o game_exe  -Wl,-rpath,/Users/matt/projects/game/build/lib  librender.a  lib/glfw-3.3.8/src/libglfw3.a  lib/libGLEW.2.1.0.dylib  -framework Cocoa  -framework IOKit  -framework CoreFoundation  -Xlinker -framework -Xlinker OpenGL && :
[build] Undefined symbols for architecture arm64:
[build]   "render::ViewRenderer<render::View>::ViewRenderer(render::View)", referenced from:
[build]       _main in main.cpp.o
[build]   "render::ViewRenderer<render::View>::~ViewRenderer()", referenced from:
[build]       _main in main.cpp.o
[build] ld: symbol(s) not found for architecture arm64
[build] clang: error: linker command failed with exit code 1 (use -v to see invocation)
[build] ninja: build stopped: subcommand failed.
[proc] The command: /opt/homebrew/bin/cmake --build /Users/matt/projects/game/build --config Debug --target all -- exited with code: 1 and signal: null
[build] Build finished with exit code 1
[cpptools] The build configurations generated do not contain the active build configuration. Using "" for CMAKE_BUILD_TYPE instead of "Debug" to ensure that IntelliSense configurations can be found
MattC
  • 73
  • 8
  • 1
    "doesn't have any symbols in the resulting librender.a file." - This is because `template void render::ViewRenderer::render() {}` doesn't define a *symbol* which can be used by the linker. It defines a *template* - a purely compile-time thing. Only after substitution of the template parameters this thing is compiled into a linker symbol. But there is no substitutions performed in your `view.cpp`. For more details read the [duplicate question](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) and its answers. – Tsyvarev Nov 24 '22 at 07:52
  • @Tsyvarev thank you! I resolved it by defining classes with all the types that will be used in the template (in this case one, the `render::View` `enum`) at the bottom of the `view.cpp` file, like this: `template class render::ViewRenderer`. – MattC Nov 24 '22 at 13:17

0 Answers0