c++macrosc++14variadic-templatesparameter-pack

How to print the line number of the caller function from a template function?


I am implementing a log function which logs along with line number of the code. The snippet of the code as follows:

#include <iostream>

using namespace std;

char m_buffer[500];

template<typename... Args>
void format_buffer(int line, const char* logData, Args... args)
{
    snprintf(m_buffer, 500, logData, line, args...);
    cout << m_buffer << endl;
}

template<int line = __builtin_LINE(), typename... Args>
void log_error(const char* logData, Args... args)
{
    format_buffer(line, logData, args...);
}

int main()
{
    float f1 = 12.35f;
    log_error("Line %d: Sample error, signal value is %f",f1);
    return 0;
}

Here, the output is as follows:

Line 15: Sample error, signal value is 12.350000

The line number of the template function prints here but not the log statement.

I tried using the __LINE__ macro as shown below:

#include <iostream>

using namespace std;

char m_buffer[500];

template<typename... Args>
void format_buffer(int line, const char* logData, Args... args)
{
    snprintf(m_buffer, 500, logData, line, args...);
    cout << m_buffer << endl;
}

template<typename... Args>
void log_error(int line, const char* logData, Args... args)
{
    format_buffer(line, logData, args...);
}

int main()
{
    float f1 = 12.35f;
    log_error(__LINE__, "Line %d: Sample error, signal value is %f",f1);
    return 0;
}

The output is:

Line 24: Sample error, signal value is 12.350000

The line number prints as the log Statement.

My concern is I don't want the user to use the __LINE__ macro in every log statement, instead I want to take the line number from the API. In fact, I don't want my function to use a format specifier for line like below.

log_error("Line %d: Sample error, signal value is %f",f1);

I want the user to use as below:

log_error("Sample error, signal value is %f",f1);

Which prints the line number automatically.

Is it possible to get the line number of the log statement without the user specifying the macro?

Note: I am using a C++14 compiler.


Solution

  • For a quick fix, you could use a macro, and also, skip having the user provide "Line %d: " since that's a required part of the format string. Add that part in your log_error function instead.

    #include <cstdio>
    #include <iostream>
    
    thread_local char m_buffer[512]; // thread local to be usable from multiple threads
    
    template <std::size_t N, class... Args>
    void log_error(int line, const char (&logData)[N], Args... args) {
        static_assert(N < sizeof(m_buffer) / 8, "suspiciously long format string");
    
        auto linelen = std::snprintf(m_buffer, sizeof m_buffer, "Line %d: ", line);
    
        if (std::snprintf(m_buffer + linelen, sizeof m_buffer - linelen,
                          logData, line, args...) > 0)
        {
            std::cout << m_buffer << std::endl;
        }
    }
    
    #define LOG_ERROR(...) log_error(__LINE__, __VA_ARGS__)
    
    int main() {
        float f1 = 12.35f;
        LOG_ERROR("Sample error, signal value is %f", f1);
    }
    

    Demo

    Otherwise, take a look at boost::source_location which is included in the standard library as std::source_location since C++20.