c++macrosc++20std-source-location

Replacing __LINE__ and __FUNCSIG__ with the new std::source_location in a macro


C++20 added std::source_location as a replacement for the debugging macros __LINE__, __FILE__, etc.

This is great. I have a macro that builds up a variable declaration in order to log and profile a block of code using said macros:

#define TOKEN_PASTE_SIMPLE(x, y) x##y
#define TOKEN_PASTE(x, y) TOKEN_PASTE_SIMPLE(x, y)
#define TOKEN_STRINGIZE_SIMPLE(x) #x
#define TOKEN_STRINGIZE(x) TOKEN_STRINGIZE_SIMPLE(x)

//...

#if defined PROFILE_LOG_SCOPE || defined PROFILE_LOG_SCOPE_FUNCTION
    #undef PROFILE_LOG_SCOPE
    #undef PROFILE_LOG_SCOPE_FUNCTION
#endif
#ifdef PROFILE_BUILD
    #define PROFILE_LOG_SCOPE(tag_str) ProfileLogScope TOKEN_PASTE(plscope_, __LINE__)(tag_str)
    #define PROFILE_LOG_SCOPE_FUNCTION() PROFILE_LOG_SCOPE(__FUNCSIG__)
#else
    #define PROFILE_LOG_SCOPE(tag_str)
    #define PROFILE_LOG_SCOPE_FUNCTION()
#endif

However, replacing the macros with the source_location version breaks because the function calls are not evaluated before the macro expansion.

#define TOKEN_PASTE_SIMPLE(x, y) x##y
#define TOKEN_PASTE(x, y) TOKEN_PASTE_SIMPLE(x, y)
#define TOKEN_STRINGIZE_SIMPLE(x) #x
#define TOKEN_STRINGIZE(x) TOKEN_STRINGIZE_SIMPLE(x)

//...

//TODO: Replace __LINE__ with std::source_location::line
//TODO: Replace __FUNCSIG__ with std::source_location::function_name
#if defined PROFILE_LOG_SCOPE || defined PROFILE_LOG_SCOPE_FUNCTION
    #undef PROFILE_LOG_SCOPE
    #undef PROFILE_LOG_SCOPE_FUNCTION
#endif
#ifdef PROFILE_BUILD
    #define PROFILE_LOG_SCOPE(tag_str) ProfileLogScope TOKEN_PASTE(plscope_, std::source_location::current().line())(tag_str)
    #define PROFILE_LOG_SCOPE_FUNCTION() PROFILE_LOG_SCOPE(std::source_location::current().function_name())
#else
    #define PROFILE_LOG_SCOPE(tag_str)
    #define PROFILE_LOG_SCOPE_FUNCTION()
#endif

QUESTION

How would I get the above to work?


Solution

  • I ultimately went with a hybrid approach. That is, Use __LINE__ to generate the variable name and pass in std::source_location::current() as a default parameter:

    
    //...
    
    class ProfileLogScope {
    public:
        explicit ProfileLogScope(const char* scopeName = nullptr, std::source_location location = std::source_location::current()) noexcept;
    
    //...
    
    };
    
    
    ProfileLogScope::ProfileLogScope(const char* scopeName, std::source_location location) noexcept
    : m_scope_name(scopeName)
    , m_time_at_creation(TimeUtils::Now())
    , m_location(location)
    {
        /* DO NOTHING */
    }
    
    ProfileLogScope::~ProfileLogScope() noexcept {
        const auto now = TimeUtils::Now();
        TimeUtils::FPMilliseconds elapsedTime = (now - m_time_at_creation);
        DebuggerPrintf(std::format("ProfileLogScope {} in file {} on line {} took {:.2f} milliseconds.\n", m_scope_name != nullptr ? m_scope_name : m_location.function_name(), m_location.file_name(), m_location.line(), elapsedTime.count()));
    }
    
    
    //...
    
    #define TOKEN_PASTE_SIMPLE(x, y) x##y
    #define TOKEN_PASTE(x, y) TOKEN_PASTE_SIMPLE(x, y)
    #define TOKEN_STRINGIZE_SIMPLE(x) #x
    #define TOKEN_STRINGIZE(x) TOKEN_STRINGIZE_SIMPLE(x)
    
    //...
    
    #if defined PROFILE_LOG_SCOPE || defined PROFILE_LOG_SCOPE_FUNCTION
        #undef PROFILE_LOG_SCOPE
        #undef PROFILE_LOG_SCOPE_FUNCTION
    #endif
    #ifdef PROFILE_BUILD
        #define PROFILE_LOG_SCOPE(tag_str) auto TOKEN_PASTE(plscope_, __LINE__) = ProfileLogScope{tag_str}
        #define PROFILE_LOG_SCOPE_FUNCTION() auto TOKEN_PASTE(plscope_, __LINE__) = ProfileLogScope{nullptr}
    #else
        #define PROFILE_LOG_SCOPE(tag_str)
        #define PROFILE_LOG_SCOPE_FUNCTION()
    #endif