I have a function with a parameter pack:
template<typename... Targs>
void tprintf(const char* format, Targs... args) {}
(the implementation shouldn't matter, just the signature). I want to add source position as default argument, using the GCC/Clang builtins. Something like
template<typename... Targs>
void tprintf(const char* format, Targs... args,
const char* file = __builtin_FILE(),
unsigned line = __builtin_LINE()) {}
This compiles, but calls to it are not passing parameters to args
as I hoped; e.g.
tprintf("%d%s", 0, "a");
gives (on Clang 10)
<source>:7:5: error: no matching function for call to 'tprintf'
tprintf("%d%s", 0, "a");
^~~~~~~
<source>:2:6: note: candidate function template not viable: no known conversion from 'const char [2]' to 'unsigned int' for 3rd argument
void tprintf(const char* format, Targs... args,
^
which seems to indicate args
is empty, 0
is file
, and "a"
is line
.
Actually, while writing the question I've found that explicitly passing Targs
works:
tprintf<int, char*>("%d%s", 0, "a");
Is it possible to avoid this?
The solution would be to us C++20's std::source_location
:
#include <iostream>
#include <source_location>
template<typename... Targs, auto location = []{ return source_location::current(); }()>
auto tprintf(char const* format, Targs const&... args) -> void {
std::cout
<< std::format("{}:{}: ", location.file_name(), location.line())
<< std::format(format, args...);
}
auto main() -> int {
tprintf("hello {}", "world"); // prints "example.cpp:12: hello world"
}
If C++20 is not an option (especially at this current time, compilers don't support source location) then there is a way to do it without macros. You can simply do a little indirection over the compiler built-ins.
You cannot put defaulted arguments after the variadic arguments, but you can put them in the constructor of the first parameter to have the same effect:
#include <cstdio>
struct location_and_format {
constexpr location_and_format(
char const* _format,
char const* _file_name = __builtin_FILE(),
unsigned _line = __builtin_LINE()
) noexcept : format{_format}, file_name{_file_name}, line{_line} {}
char const* format;
char const* file_name;
unsigned line;
};
template<typename... Targs>
void tprintf(location_and_format format, Targs... args) {
printf("%s:%u: ", format.file_name, format.line);
printf(format.format, args...);
}
int main() {
tprintf("hello %s", "world"); // prints example.cpp:22: hello world
}