0

The below is a simple logger class I implemented my project.

/* g++ test.cpp -o test */

#include <fstream>
#include <string>

#include "fmt/fmt.h"

typedef enum Severity
{
    LOG_INFO = 0,
    LOG_WARNING,
    LOG_ERROR
} Severity;

class Logger
{
public:
    Logger(const std::string &log_file);
    ~Logger();

    template <typename... Args> void DoLog(Severity severity, const std::string& context, fmt::format_string<Args...> fmt, Args&&...args);
private:
    std::ofstream fout;
};

static const std::string severity_str[3] = {
    "INFO",
    "WARNING",
    "ERROR"
};

Logger::Logger(const std::string &log_file)
{
    fout.open(log_file, std::ofstream::out | std::ofstream::trunc);
}

Logger::~Logger()
{
    fout.close();
}

template <typename... Args>
void Logger::DoLog(Severity severity, const std::string& context, fmt::format_string<Args...> fmt, Args&&...args)
{
    fout << fmt::format("[{:<7}] [{}] {}", severity_str[severity], context, fmt::format(fmt, std::forward<Args>(args)...)) << std::endl;
}

int main()
{
    Logger log("log.txt");
    log.DoLog(LOG_INFO, "0", "Yo");
    log.DoLog(LOG_INFO, "1", "Hello {}", 42);
    log.DoLog(LOG_ERROR, "2", "Word {0:02X}", 0xFF);
    log.DoLog(LOG_WARNING, "3", "! {} {}", 4, 5);
    uint8_t opcode = 0xDD;
    log.DoLog(LOG_ERROR, "CPU::HandlePrefixCB", "Unknown opcode: {0:02X}", opcode);
    uint8_t &ref = opcode;
    log.DoLog(LOG_ERROR, "CPU::HandlePrefixCB", "Unknown opcode: {0:02X}", ref);
    return 0;
}

Even though this file compiles and runs without problems, when I add them to my project codebase and compile, g++ outputs a bunch of undefined references like ones below

C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: obj/CPUOpcodes.o:CPUOpcodes.cpp:(.text+0xf47): undefined reference to `void Logger::DoLog<unsigned char&>(Severity, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, fmt::v8::basic_format_string<char, fmt::v8::type_identity<unsigned char&>::type>, unsigned char&)'
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: obj/CPUOpcodes.o:CPUOpcodes.cpp:(.text+0x3011): undefined reference to `void Logger::DoLog<unsigned char&>(Severity, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, fmt::v8::basic_format_string<char, fmt::v8::type_identity<unsigned char&>::type>, unsigned char&)'
...

I suspect the problem lies on how I handle varags fmt::format_string<Args...> fmt, Args&&...args in DoLog. I can't find anything wrong with it. Is there anything I should fix?

Ray Siplao
  • 199
  • 8
  • 2
    Are you, by any chance, declaring a template function in a header and defining it in a source file? See [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021) – Igor Tandetnik Aug 15 '21 at 04:31
  • 1
    "Even though this file compiles and runs without problems" This is indication that the code is not a [example]. – user202729 Aug 15 '21 at 04:32
  • @IgorTandetnik They can work like this in source file only approaches like this example. But for reusability they should indeed be implemented in header files. And in this case it is not the cause of his issue :) – Pepijn Kramer Aug 15 '21 at 05:42

1 Answers1

0

The third parameter to DoLog should just be a string (I changed output to std::cout for testing)

#include <map>
#include <iostream>
#include <string>

#include <fmt/format.h>


typedef enum Severity
{
    LOG_INFO = 0,
    LOG_WARNING,
    LOG_ERROR
} Severity;

static const std::string severity_str[3] = 
{
    "INFO",
    "WARNING",
    "ERROR"
};

class Logger
{
public:
    Logger() = default;
    ~Logger() = default;

    template<typename... args_t>
    void DoLog(Severity severity, const std::string& context, const std::string& fmt, args_t&&... args)
    {
        std::cout << fmt::format("[{:<7}] [{}] {}", severity_str[severity], context, fmt::format(fmt, std::forward<args_t>(args)...)) << std::endl;
    }

};


int main()
{
    Logger log;
    log.DoLog(LOG_INFO, "0", "Yo");
    log.DoLog(LOG_INFO, "1", "Hello {}", 42);
    log.DoLog(LOG_ERROR, "2", "Word {0:02X}", 0xFF);
    log.DoLog(LOG_WARNING, "3", "! {} {}", 4, 5);
    uint8_t opcode = 0xDD;
    log.DoLog(LOG_ERROR, "CPU::HandlePrefixCB", "Unknown opcode: {0:02X}", opcode);
    uint8_t& ref = opcode;
    log.DoLog(LOG_ERROR, "CPU::HandlePrefixCB", "Unknown opcode: {0:02X}", ref);
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19