C++ 17 变长模板包到运行时






4.68/5 (8投票s)
通过变长模板减少函数递归
引言
在我的上一篇 C++ 17 文章 这里 中,我不太喜欢 std::any。 然而,我想到一个有趣的技巧,可以减少可变模板函数中所需的递归。
C++ 11 和 C++ 17
首先,旧的方法。来自 Cake Processor 的一篇 优秀文章,关于可变参数 printf,我们有以下代码:
void safe_printf(const char *s)
{
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
}
else {
throw std::runtime_error("invalid format string: missing arguments");
}
}
std::cout << *s++;
}
}
template<typename T, typename... Args>
void safe_printf(const char *s, T value, Args... args)
{
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
}
else {
std::cout << value;
safe_printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
return;
}
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
两个函数。一个可变模板函数,另一个是最后的 const char* 函数,当参数包中的所有参数都展开后,它会被调用。 最重要的困难是必须在所有此类模板中进行的编译时递归。
但是,请记住参数包使用逗号分隔符展开参数
template <typename ... Args> void foo(Args ... args) { } foo(1,2,3,"hello"); // means that args is unpacked as such.
为什么不使用 initializer_list 将这些值存储在 vector 中,以便在运行时使用 for 循环访问它们呢?
template <typename ... Args> void foo(Args ... args) { std::vector<std::any> a = {args ...}; }
非常酷。 事实是这些项存储在 std::any 中,意味着任何东西都可以传递给它,现在可以在运行时访问它。 请注意,这可行是因为 std::any 本身不是一个模板;它将包含的对象存储为通用指针,并通过 typeinfo 测试它是否与传递给它的类型匹配。 顺便说一句,我认为类型测试应该放宽,例如,如果你有一个包含 int 的 std::any,为什么不能使用 any_cast<long>() 返回它呢?
现在新的 printf 在一个函数中(好的,为了效率,人们会循环遍历 vector,但现在这并不重要)
template<typename ... many>
void safe_printf2(const char *s, many ... args)
{
vector<any> a = {args ...};
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
}
else {
if (a.empty())
throw std::logic_error("Fewer arguments provided to printf");
if (a[0].type() == typeid(string)) cout << any_cast<string>(a[0]);
if (a[0].type() == typeid(int)) cout << any_cast<int>(a[0]);
if (a[0].type() == typeid(double)) cout << any_cast<double>(a[0]);
a.erase(a.begin());
s++;
}
}
std::cout << *s++;
}
}
// ---- int main() { safe_printf2("Hello % how are you today? I have % eggs and your height is %","Jack"s, 32,5.7); // Hello Jack how are you today? I have 32 eggs and your height is 5.7 }
通常,使用 vector<any> 允许你将参数包扩展到运行时,并使用 for 循环对其进行操作。
当然,initializer_list 会复制所有参数。 所以你可能想使用指针
vector<any> a = {&args...}; if (a[0].type() == typeid(string*)) cout << *any_cast<string*>(a[0]);
或者,引用,借助 std::ref 和 std::reference_wrapper(我更喜欢指针。 此外,std::reference_wrapper 编写起来更长,理解起来更困难,而且它内部也使用指针,因为它不能使用其他任何东西。可耻啊)
vector<any> a = {std::ref(args)...}; if (a[0].type() == typeid(std::reference_wrapper<string>)) cout << any_cast<std::reference_wrapper<string>>(a[0]).get();
历史
2018年1月6日:首次发布