Using std::source_location for C++ Debugging
Introduced in C++20, std::source_location provides a
standardized, type-safe mechanism to capture file names, line numbers,
column numbers, and function names at compile time. This article
explores how std::source_location replaces legacy
preprocessor macros, simplifies logging and assertion frameworks, and
improves the accuracy of diagnostic data during software
development.
What Is std::source_location?
Found in the <source_location> header,
std::source_location is a utility struct that encapsulates
information about the source code location where an operation occurs.
Historically, C and C++ developers relied on preprocessor macros such as
__FILE__, __LINE__, and __func__
to track code execution points. While functional, these macros pollute
the global namespace, lack type safety, and often require wrapping
functions in additional macros to capture call-site information rather
than callee information.
std::source_location eliminates these shortcomings by
generating context-aware source metadata directly through normal C++
function parameter mechanisms.
Key Member Functions
An instance of std::source_location exposes several
member functions to query context:
line(): Returns an unsigned integer representing the line number in the source file.column(): Returns an unsigned integer representing the column number where the call originated.file_name(): Returns a null-terminated C-string containing the path to the source file.function_name(): Returns a null-terminated C-string containing the signature or name of the enclosing function.
Capturing Call-Site Information
The primary strength of std::source_location lies in
std::source_location::current(). When used as a default
function argument, current() evaluates at the location of
the caller rather than the function definition.
#include <iostream>
#include <source_location>
#include <string_view>
void log_message(std::string_view message,
const std::source_location& location = std::source_location::current()) {
std::cout << "[LOG] "
<< location.file_name() << ':'
<< location.line() << " ("
<< location.function_name() << "): "
<< message << '\n';
}
int main() {
log_message("Application started."); // Logs main() and its line number
return 0;
}Because default arguments are evaluated at the call site, developers
no longer need custom #define LOG(msg) macros to record the
origin of an event.
Role in Debugging and Diagnostics
1. Cleaner Logging Infrastructures
Logging libraries can capture precise call traces without imposing macro syntax on consumers of the API. This enables modern syntax like method chaining, lambda integration, and standard template specialization without breaking source reporting.
2. Enhanced Assertions and Exception Handling
Custom assert mechanisms and exceptions can store a
std::source_location object directly. When an unexpected
condition triggers an exception, the handler can print the exact line
and function where the error occurred, vastly reducing time spent
reproducing bugs.
#include <stdexcept>
#include <source_location>
class detailed_runtime_error : public std::runtime_error {
public:
detailed_runtime_error(const std::string& what,
std::source_location loc = std::source_location::current())
: std::runtime_error(what), location_(loc) {}
const std::source_location& where() const noexcept { return location_; }
private:
std::source_location location_;
};3. Zero Runtime Overhead
Instances of std::source_location are
constexpr by design. Metadata collection happens during
compilation, meaning that querying location details introduces no
unexpected runtime performance costs beyond passing the values to the
diagnostic handler.
Summary
The role of std::source_location in C++ debugging is to
bring source code introspection into the core type system. By replacing
fragile preprocessor macros with modern default arguments, it provides a
clean, zero-overhead, and type-safe foundation for application logging,
error reporting, and runtime diagnostics.