Forwarding a Variable Number of Arguments to SpdLog in a C++ Module

103 Views Asked by At

I am a professional C# developer and am starting a C++ side project. One of my first steps has been to get logging up and running using spdlog. As this is a new modern project, I am using modules.

I have written a Meyers Singleton class intended to encapsulate spdlog and hide it entirely within a module so that at any time in the future I could swap out a different logging library and no client code would need to be changed. Any calling code just needs to import my "Logger" wrapper module and start logging away.

The logger is part of a shared library ("Core") that uses spdlog built as a static library.

I have the basic functionality working, but I need to implement the ability to forward a variable number of arguments sent to my wrapper method to the corresponding spdlog methods (trace(), info(), etc.).

A client should be able to use the code like this:

import Logger;

int main()
{
    //This is a static method that calls my singleton wrapper.
    Logger::Info("{1} is a {0}", "test", "This");
}

When calling the info() member function on a spdlog instance, this call works fine. For example:

//This works fine calling on spdlog directly.
directSpdlogInstance->info("{1} is a {0}", "test", "This");

But if I try the static call on my singleton wrapper "Logger" as above, I get errors. The signature of the spdlog function I'm trying to call from within my wrapper is this:

template <typename... Args>
inline void info(format_string_t<Args...> fmt, Args &&...args);

Here are some implementations I've tried along with their errors:

template<typename...Args>
void Logger::Info(Args&&... args)
{
    //GetInstance() retrieves a spdlog logger instance.
    GetInstance()->info(std::forward<Args>(args)...);
}

When I try this I get compile error C7595:

'fmt::v9::basic_format_string<char,const char (&)[32]>::basic_format_string': call to immediate function is not a constant expression Nano C:\Dev\Nano\Projects\Core\src\Logger.cppm 30

OK. So it seems I can't simply forward the arguments. It also seems spdlog is using the fmt library. So I found a preprocessor #define for spdlog "SPDLOG_USE_STD_FORMAT" which seems to switch it to use the std library version of format.

With this define in place, I tried implementing it like this:

template<typename...Args>
void Logger::Info(std::format_string<Args...> fmt, Args&&... args)
{
    GetInstance()->info(fmt, std::make_format_args(args...));
}

I get compiler error C2664:

'void spdlog::logger::info<std::_Format_arg_store<_Context,const char (&)[32]>>(std::basic_format_string<char,std::_Format_arg_store<_Context,const char (&)[32]>>,std::_Format_arg_store<_Context,const char (&)[32]> &&)': cannot convert argument 1 from 'std::basic_format_string<char,const char (&)[32]>' to 'std::basic_format_string<char,std::_Format_arg_store<_Context,const char (&)[32]>>' with [ _Context=std::format_context ] Nano C:\Dev\Nano\Projects\Core\src\Logger.cppm 34

Edit: I think I've got a little further now. If I implement the function like this:

template<typename...Args>
void Logger::Info(std::format_string<Args...> fmt, Args&&... args)
{
    GetInstance()->info(fmt, std::forward<Args>(args)...);
}

then the project compiles successfully: 0 errors, 0 warnings!

But now I get an error in the Console application project that is USING the logger. It's linker error LNK1104:

cannot open file 'C:\Dev\Nano\bin\windows-Debug\Core\Core.lib'

My dll file is getting built, but there's no corresponding lib file now! I understand this generally happens if there's nothing that gets exported from the dll. But clearly my module code is exporting the public static method:

export class Logger
{
public:
    Logger(const Logger& other) = delete;

    template<typename...Args>
    NN_API static void Info(std::format_string<Args...> fmt, Args&&... args);
...

and the main() function is calling it as described above. NN_API is a pretty standard __declspec(dllexport) define.

I feel like I'm close but can't make it across the finish line. Can someone please help?

1

There are 1 best solutions below

0
Todd Burch On

OK, so iterating though my attempts above I ultimately reached the syntax that compiles properly, and @Jarod42 above pointed out why the function isn't exported. It's obvious in retrospect!

The reason the variadic function isn't visible to consumers of the DLL is because it's not being generated. Since this is a shared library (DLL) which is linked with a consuming application at runtime, there is no way for the compiler to know which versions of the variadic function to instantiate!

So to fix the final issue, I believe the logger will need to be turned into a static library so that all of the instantiations of the variadic function can be determined at compile time.