C++ProgrammingSenior C++ Developer

Unpack the compiler intrinsic mechanism that enables std::source_location::current() to capture call site metadata while simultaneously preventing manual construction of arbitrary source coordinates.

Pass interviews with Hintsage AI assistant

Answer to the question

History: Prior to C++20, developers relied on preprocessor macros such as __FILE__ and __LINE__ to capture source code metadata for logging and debugging. These macros suffered from expansion context issues, namespace pollution, and inability to propagate through abstraction layers without code generation tricks. The C++20 standard introduced std::source_location to provide a type-safe, constexpr-compatible alternative that captures call site information automatically.

The Problem: When wrapping logging functionality in helper functions, macro-based approaches capture the location of the wrapper definition rather than the actual call site, rendering them useless for pinpointing errors in deep call stacks. Additionally, manual propagation of source metadata through every function signature creates invasive API changes and maintenance burdens. There was a need for a mechanism that captures filename, line number, column, and function name at the point of invocation without explicit parameter passing.

The Solution: std::source_location is a trivially copyable struct with a private constructor that can only be instantiated by the compiler through its static member function current(). When used as a default argument to a function parameter, std::source_location::current() is evaluated at the call site rather than the definition site, utilizing compiler intrinsics to populate its fields with the exact source coordinates. This design prevents manual construction of arbitrary source locations, ensuring diagnostic integrity while enabling seamless propagation through template instantiations and callback chains.

#include <source_location> #include <iostream> #include <string> class Logger { public: static void log(const std::string& message, std::source_location loc = std::source_location::current()) { std::cout << loc.file_name() << ":" << loc.line() << " [" << loc.function_name() << "] " << message << std::endl; } }; void process_data(int value) { if (value < 0) { Logger::log("Invalid value received"); // Captures this line, not Logger::log definition } }

Situation from life

Context: A high-frequency trading system required distributed logging where error reports must pinpoint the exact origin line across millions of lines of code, including through templated algorithms and lambda callbacks. The existing codebase used a macro-based LOG_ERROR() that expanded __FILE__ and __LINE__, but this broke when developers introduced helper functions like validate_input() that internally called the logger, causing all errors to report the helper's internal line rather than the business logic call site.

Problem: The macro expansion captured the location where the logging call was physically written in the source, not the logical error location. When validate_input() was called from 500 different places, all 500 errors reported the same file and line inside the validation function. This made production debugging nearly impossible during race condition investigations.

Solutions Considered:

Option 1: Macro Propagation with Explicit Parameters. We considered forcing every function to accept const char* file, int line parameters through a variadic macro wrapper that injected these at every call site. Pros: Maintains accurate location information through arbitrary call depths. Cons: Massive API pollution, breaks third-party library interfaces, increases compile times significantly, and prevents usage in constexpr contexts where macros are forbidden.

Option 2: Runtime Stack Unwinding with Debug Symbols. Implement a runtime stack trace capture using platform-specific APIs like backtrace() on POSIX or CaptureStackBackTrace on Windows, then resolve addresses to line numbers using debug symbols. Pros: Non-invasive to APIs, captures full call stack. Cons: Extreme runtime overhead (unsuitable for high-frequency paths), requires shipping debug symbols to production, and resolution is asynchronous and unreliable under crash conditions.

Option 3: std::source_location with Default Arguments. Replace the macro with a function accepting std::source_location loc = std::source_location::current() as the last parameter. Pros: Zero runtime overhead (constexpr construction), automatic propagation through templates, captures column information for precise diagnostics, and respects namespace scopes without pollution. Cons: Requires C++20 compiler support, and developers must remember to place it as a default argument (not inside the function body where it would capture the function's internal location).

Chosen Solution and Result: We selected Option 3 because the trading system was migrating to C++20 anyway, and the constexpr nature of std::source_location allowed compile-time verification of log format strings while maintaining nanosecond-level performance requirements. After implementation, error reports contained exact line numbers like trading_engine.cpp:847 [auto execute_order(const Order&)::(lambda)], enabling us to identify a critical race condition in two hours instead of two days. The restriction that std::source_location cannot be manually constructed prevented junior developers from accidentally passing fabricated locations during testing, ensuring production logs remained forensically trustworthy.

What candidates often miss

Why is std::source_location::current() special when used as a default argument, and what happens if you call it inside the function body instead?

When std::source_location::current() appears as a default argument, the C++20 standard mandates that the compiler evaluates it at the call site, substituting the line where the function is invoked. If placed inside the function body, it evaluates to the location of that specific line inside the function definition, rendering it useless for call site attribution. This behavior is a special case in the language specification for this specific function; regular default arguments are evaluated at the definition site, but std::source_location receives this unique treatment to enable automatic logging. Beginners often place auto loc = std::source_location::current(); as the first line of their logging function, then wonder why every log entry points to the same internal line.

Can you manually construct a std::source_location with arbitrary file and line numbers, and why does the standard prevent this?

No, you cannot manually construct a valid std::source_location because its constructors are private and accessible only to the implementation. The standard enforces this restriction to maintain the integrity of diagnostic information, preventing developers from spoofing or fabricating source locations in security-critical logging systems. While you might want to simulate locations for unit testing log outputs, the standard committee prioritized forensic reliability over testing flexibility. The only way to obtain an instance is through current(), which is implemented as a compiler intrinsic that populates the struct's private fields with the actual translation unit's internal representation.

Does std::source_location work correctly within lambda expressions, template instantiations, and inlined functions, and what specific metadata does it capture?

Yes, std::source_location functions correctly in all these contexts, but candidates often miss the nuances. For lambdas, function_name() returns the implementation-defined name (often something like operator() or the lambda's internal symbol), while file_name() and line() point to the lambda's definition site in the source. In template instantiations, each distinct instantiation generates its own source location pointing to the specific template arguments used. The struct captures four pieces of metadata: file_name() (const char*), line() (uint_least32_t), column() (uint_least32_t, often underestimated but crucial for macro-heavy code), and function_name() (const char*). Many candidates are unaware of column(), which distinguishes between multiple macro invocations on the same physical line, or they assume function_name() returns demangled symbols (it actually returns the implementation's raw function signature).