I am writing a log function that takes a format string and a variable number of arguments and captures std::source_location. This is not straightforward, so I tried the FormatWithLocation solution described in How to use source_location in a variadic template function? - which works.
Now, I want to process std::source_location::function_name() - at compile time. And I don't really see a way to create a compile-time string and use it at runtime without actually hardcoding the string.
This is what I tried (in a much simplified version which doubles each charater in the input function name). I resorted to a bunch of tricks, including using std::array for compile-time strings, but for those I need to know the size in advance; with any other data structure, I cannot seem to use compile-time heap-allocated data in the run-time world (understandably).
#include <array>
#include <iostream>
#include <source_location>
#include <string>
// TODO: avoid hardcoded size
constexpr size_t N = 61;
struct StringWithSourceLocation {
template <size_t inputLength>
static consteval std::array<char, inputLength> toArray(
const char* const inputText) {
std::array<char, inputLength> array{};
for (size_t i = 0; i < inputLength; i++) array[i] = inputText[i];
return array;
}
template <size_t inputLength>
static consteval size_t computeLength(
const std::array<char, inputLength>& inputText) {
return inputLength * 2 + 1;
}
template <size_t inputLength, size_t outputLength>
static consteval std::array<char, outputLength> produceOutput(
const std::array<char, inputLength>& inputText) {
std::array<char, outputLength> outputText{};
size_t outputIndex = 0;
for (const char c : inputText) {
outputText[outputIndex++] = c;
outputText[outputIndex++] = c;
}
outputText[outputIndex++] = 0;
return outputText;
}
static consteval std::array<char, N> process(
const std::source_location sourceLocation) {
// TODO: work without quotation marks
constexpr const char* functionName = "sourceLocation.function_name()";
constexpr size_t inputLength =
std::string::traits_type::length(functionName);
constexpr auto input = toArray<inputLength>(functionName);
constexpr size_t outputLength = computeLength(input);
constexpr std::array<char, N> outputText =
produceOutput<inputLength, outputLength>(input);
return outputText;
}
consteval StringWithSourceLocation(
const char* message,
const std::source_location sourceLocation =
std::source_location::current(),
const std::array<char, N> processedOutputName =
process(std::source_location::current()))
: message(message),
sourceLocation(sourceLocation),
processedFunctionName(processedOutputName) {}
const char* message;
const std::source_location sourceLocation;
const std::array<char, N> processedFunctionName;
};
static void log(const StringWithSourceLocation foo) {
// This shall return a compile-time result:
std::cout << foo.processedFunctionName.data() << ": " << foo.message
<< std::endl;
}
int main() { log("bar"); }
Output:
ssoouurrcceeLLooccaattiioonn..ffuunnccttiioonn__nnaammee(()): bar
So the code above works, but you see two TODOs in this code:
- Somehow, the hardcoded size needs to be converted into a template parameter of the class which is deduced from the input
source_location. - The input
source_locationshould actually be used in processing, instead of the hardcoded string.
Is this possible at all?
(This is for MSVC 17.10, if this is relevant at all.)