组合函数参数包和默认参数
Combining function parameter pack and default arguments
我有一个带有参数包的函数:
template<typename... Targs>
void tprintf(const char* format, Targs... args) {}
(实现不重要,重要的是签名)。我想使用 GCC/Clang 内置函数将源位置添加为默认参数。像
template<typename... Targs>
void tprintf(const char* format, Targs... args,
const char* file = __builtin_FILE(),
unsigned line = __builtin_LINE()) {}
这可以编译,但调用它并没有像我希望的那样将参数传递给 args
;例如
tprintf("%d%s", 0, "a");
给出(在 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,
^
似乎表明args
是空的,0
是file
,"a"
是line
。
实际上,在写问题时我发现显式传递 Targs
有效:
tprintf<int, char*>("%d%s", 0, "a");
是否可以避免这种情况?
使用可变参数宏怎么样?
template<typename... Targs>
void tprintfhelper(const char* f, int l, const char* format, Targs... )
{
std::cout << format << " " << l << " " << f << std::endl;
}
#define tprintf(...) tprintfhelper(__builtin_FILE(), __builtin_LINE() __VA_OPT__(,) ##__VA_ARGS__)
int main()
{
tprintf("%d", 1, 2, 3, 4, 5, "dog");
}
我们的解决方案是 C++20 的 std::source_location
:
#include <iostream>
#include <source_location>
template<typename... Targs, auto location = 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"
}
如果 C++20 不是一个选项(尤其是目前,编译器不支持源代码位置),那么有一种方法可以在没有宏的情况下完成。您可以简单地对编译器内置函数进行一些间接处理。
不能将默认参数放在可变参数之后,但可以将它们放在第一个参数的构造函数中以达到相同的效果:
#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
}
我有一个带有参数包的函数:
template<typename... Targs>
void tprintf(const char* format, Targs... args) {}
(实现不重要,重要的是签名)。我想使用 GCC/Clang 内置函数将源位置添加为默认参数。像
template<typename... Targs>
void tprintf(const char* format, Targs... args,
const char* file = __builtin_FILE(),
unsigned line = __builtin_LINE()) {}
这可以编译,但调用它并没有像我希望的那样将参数传递给 args
;例如
tprintf("%d%s", 0, "a");
给出(在 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,
^
似乎表明args
是空的,0
是file
,"a"
是line
。
实际上,在写问题时我发现显式传递 Targs
有效:
tprintf<int, char*>("%d%s", 0, "a");
是否可以避免这种情况?
使用可变参数宏怎么样?
template<typename... Targs>
void tprintfhelper(const char* f, int l, const char* format, Targs... )
{
std::cout << format << " " << l << " " << f << std::endl;
}
#define tprintf(...) tprintfhelper(__builtin_FILE(), __builtin_LINE() __VA_OPT__(,) ##__VA_ARGS__)
int main()
{
tprintf("%d", 1, 2, 3, 4, 5, "dog");
}
我们的解决方案是 C++20 的 std::source_location
:
#include <iostream>
#include <source_location>
template<typename... Targs, auto location = 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"
}
如果 C++20 不是一个选项(尤其是目前,编译器不支持源代码位置),那么有一种方法可以在没有宏的情况下完成。您可以简单地对编译器内置函数进行一些间接处理。
不能将默认参数放在可变参数之后,但可以将它们放在第一个参数的构造函数中以达到相同的效果:
#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
}