I am integrating a third-party library into my project. The library provides hooks to redirect its log messages and provides a struct with file, line, severity, message, etc. My project uses std::source_location
for logging. How do I construct std::source_location from the values provided by the library? I want the original file/line to show up in my logs rather than the hook function. Thanks
constructing std::source_location with custom values
300 views Asked by Tomas AtThere are 3 answers
This is not generally possible; the only standardized ways to obtain a source_location
are its default constructor (which gives you an empty object) and current()
(which gives you an object with values corresponding to the call site). There are no standardized setters, so you cannot portably modify the field values of an existing source_location
.
If we examine major implementations: MS-STL, libstdc++, libc++; you can see that while it is possible to supply arguments to current()
these are different in each case (so the code would be non-portable, even targeting just these three implementations, notwithstanding that the interface could be changed at any time, since it is not intended to be called by user code) and in the case of libstdc++ the type to be passed in is private. In addition, the source_location
type does not have ownership semantics so you would need to ensure lifetime to avoid dangling pointers. Finally, current()
is consteval
so you would not be able to call it with runtime values in any case.
That leaves only modifying the private data of a source_location
instance; while possible via the usual tricks, this would still have lifetime issues and would be fragile and have undefined behavior since modifying Standard library objects is not permitted.
It would be better to switch your library to use a source location class designed to be constructible with runtime values. For example, you could use boost::source_location
, or write your own.
namespace notstd {
struct source_state {
std::uint_least_t line = 0;
std::uint_least_t column = 0;
std::string file;
std::string function;
};
struct source_location : std::source_location {
std::optional<source_state> ss;
constexpr source_location(std::source_location const& base):
std::source_location(base)
{}
source_location(source_state manual):
ss(std::move(manual))
{}
constexpr std::uint_least_t line() const {
if (ss) return ss->line;
return std::source_location::line();
}
constexpr std::uint_least_t column() const {
if (ss) return ss->column;
return std::source_location::column();
}
char const* file_name() const {
if (ss) return ss->file.c_str();
return std::source_location::file_name();
}
char const* function_name() const {
if (ss) return ss->function.c_str();
return std::source_location::function_name();
}
};
}
this is a pretty close to drop-in replacement for std::source_location
that permits you to manually specify parameters.
Simply replace your own use of std::source_location
with the above. Your existing code will probably compile. (the biggest API difference is that file name/function name are no longer constexpr; that is hard to pull off, so I didn't)
And now you can use notstd::source_state
to pass in a manual location.
You don't.
std::source_location
is specifically designed to ensure that any given instance got its data from the compiler, not "values provided by the library". This requires that the object is created by the library's source code internally. If it wasn't written to do that, then you can't get asource_location
for it.You need to create an overload of your logging function that can take the values directly as opposed to using
source_location
.