constructing std::source_location with custom values

313 views Asked by At

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

3

There are 3 answers

0
Nicol Bolas On

How do I construct std::source_location from the values provided by the library?

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 a source_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.

0
ecatmur On

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.

3
Yakk - Adam Nevraumont On
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.