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.