Home > Software engineering >  Deducing variadic template arguments with default arguments
Deducing variadic template arguments with default arguments

Time:06-06

I am trying to create a basic logger. Here is a small example.

template<typename ...str>
struct log {
    log(
        str &&...args,
        const char  *file = __builtin_FILE(),
        const char  *func = __builtin_FUNCTION(),
        const size_t line = __builtin_LINE()
    ) {

        std::cout << "[" << file << "] [" << func << "] [" << line << "] ";
        ((std::cout << args << " "), ...);
        std::cout << std::endl;
    }
};

template<typename ...str>
log(str &&...args) -> log<str ...>;

I want the ability to receive caller information along with a variable number of arguments. With the above example, I can create an instance like this.

log inst("THIS WORKS", "ASD", "ASD");

>>> [.../main.cpp] [main] [10] THIS WORKS ASD ASD

I also want the ability to specify logging levels. This is where the trouble begins. Say I have an enum with the following logging levels, and I want to make it a template argument for the logger. The following is how I thought this would work.

enum logging_level {
    INFO,
};

template<logging_level level, typename ...str>
struct log {
    ...
};

template<logging_level level, typename ...str>
log(str &&...args) -> log<level, str ...>;

log<INFO> inst("THIS DOESN'T WORK", "ASD", "ASD");

The error I get here is that the arguments are passed into the default arguments, not the variadic arguments.

>>> error: invalid conversion from ‘const char*’ to ‘size_t’ {aka ‘long unsigned int’} [-fpermissive]
>>>    33 |     log<INFO> inst("THIS DOESN'T WORK", "ASD", "ASD");
>>>       |                                                ^~~~~
>>>       |                                                |
>>>       |                                                const char*

This is getting well above my C template knowledge. An easy solution is to ditch the variadic template and just pass in a string but I wanted to see if this could work. Does anyone know how to make this compile?

Thanks.

CodePudding user response:

You cannot use CTAD on some arguments, but not on the others. You could make a similar syntax work though using tag dispatch. You need to move the specification of the log level to the argument list to accomplish this.

Note that in the following example I'm using std::source_location (C 20) to be compiler independent:

enum class LogLevel
{
    INFO
};

template<LogLevel logLevel>
struct LogLevelInfoTag {
    constexpr operator LogLevel()
    {
        return logLevel;
    }
};

constinit LogLevelInfoTag<LogLevel::INFO> INFO;

template<LogLevel logLevel, typename ...str>
struct log {
    log(
        LogLevelInfoTag<logLevel>,
        str &&...args,
        std::source_location location = std::source_location::current()
    ) {

        std::cout << "[" << location.file_name() << "] [" << location.function_name() << "] [" << location.line() << "] ";
        ((std::cout << args << " "), ...);
        std::cout << std::endl;
    }
};

template<LogLevel logLevel, typename ...str>
log(LogLevelInfoTag<logLevel>, str &&...args) -> log<logLevel, str ...>;

// log<INFO> inst("THIS DOESN'T WORK", "ASD", "ASD"); // (desired syntax)
log inst(INFO, "THIS DOESN'T WORK", "ASD", "ASD");
  • Related