0

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?

Quentin
  • 62,093
  • 7
  • 131
  • 191
Casey
  • 10,297
  • 11
  • 59
  • 88
  • Pass `function_name()` and `line()` as discrete, separate parameters to the logging function, and let it do all the formatting? – Sam Varshavchik Dec 25 '22 at 15:11
  • @SamVarshavchik That would work if the use case was using function arguments. The macro concatenates `plscope_` and the current line number in order to create a semi-unique variable declaration, for example: `ProfileLogScope plscope_123(tag_str)`. This allows calling `PROFILE_LOG_SCOPE` multiple times. – Casey Dec 25 '22 at 16:05
  • There's no way to evaluate `std::source_location` (or any C++ expression for that matter) at preprocessing time, so you'll have to keep living with macros for token-pasting. – Quentin Dec 25 '22 at 16:11
  • 2
    That's the whole point: get rid of the macro. The sole reason for `std::source_location` to exist is to completely eliminate the need for these kinds of macros. Pass two, discrete, parameters to the constructor, and have it deal with them. – Sam Varshavchik Dec 25 '22 at 16:17
  • @SamVarshavchik that doesn't help with naming the generated variable, though? – Quentin Dec 26 '22 at 18:55
  • I see no reason for generated variable names to be different. – Sam Varshavchik Dec 26 '22 at 19:29
  • @SamVarshavchik If I were to attempt to profile an outer scope and an inner scope without unique names the program will fail to compile with "outer declaration hides/shadows inner declaration". – Casey Dec 27 '22 at 06:38
  • This is a warning message, you must have a compilation option sets that turns it into an error. Check your compiler's documentation for a compiler-specific option or setting to turn it off. – Sam Varshavchik Dec 27 '22 at 11:55
  • @SamVarshavchik You mean to tell me you *don't* treat warnings as errors nor fix all your warnings in your code? – Casey Dec 27 '22 at 16:26
  • I do, but this is not something that I have configured as a warning. A quick test reveals that gcc does not report it with `-Wall -Wpedantic`. A quick check of gcc's docs shows that this warning must be explicitly, enabled, individually. Even gcc's developers, it seems, do not consider this to have much of a merit to be included even in `-Wpedantic`. – Sam Varshavchik Dec 27 '22 at 16:52

1 Answers1

0

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
Casey
  • 10,297
  • 11
  • 59
  • 88