c++ - 用另一个模板重载可变参数模板函数
问题描述
我试图弄清楚如何用“更专业”的可变参数函数模板“重载”可变参数函数模板。例如:
#include <iostream>
template <typename... ARGS_>
void foo(void(*fn)(ARGS_...)) {
std::cout << "Generic function pointer foo" << std::endl;
}
struct Test {
};
template <typename... ARGS_>
void foo(void(*fn)(ARGS_..., Test*)) {
std::cout << "Test function pointer foo" << std::endl;
}
void test1(int a, int b) {
std::cout << "test1()" << std::endl;
}
void test2(int a, int b, Test* x) {
std::cout << "test2()" << std::endl;
}
int main() {
foo(&test1);
foo(&test2);
return 0;
}
这段代码的输出是:
Generic function pointer foo
Generic function pointer foo
而不是:
Generic function pointer foo
Test function pointer foo
如我所愿。
从概念上讲,我试图指出“如果您有任何类型的参数,其中最后一个是 Test*,则使用模板方法 A,如果最后一个类型不是 Test*,则使用模板方法 B。”
完成这种行为的正确方法是什么?
解决方案
基于最后一个参数包参数的 SFINAE 重载
您可以根据可变参数包中的最后一个类型是否为互斥重载Test*
:
#include <type_traits>
template <typename... Ts>
using last_t = typename decltype((std::type_identity<Ts>{}, ...))::type;
struct Test {};
template <
typename... ARGS_,
std::enable_if_t<!std::is_same_v<last_t<ARGS_...>, Test *>> * = nullptr>
void foo(void (*fn)(ARGS_...)) {
std::cout << "Generic function pointer foo" << std::endl;
}
template <
typename... ARGS_,
std::enable_if_t<std::is_same_v<last_t<ARGS_...>, Test *>> * = nullptr>
void foo(void (*fn)(ARGS_...)) {
std::cout << "Test function pointer foo" << std::endl;
}
// Special case for empty pack (as last_t<> is ill-formed)
void foo(void (*fn)()) { std::cout << "Zero args" << std::endl; }
将 C++20std::type_identity
用于last_t
转换特征。
用作:
void test1(int, int b) {}
void test2(int, int b, Test *x) {}
void test3(Test *) {}
void test4() {}
int main() {
foo(&test1); // Generic function pointer foo
foo(&test2); // Test function pointer foo
foo(&test3); // Test function pointer foo
foo(&test4); // Zero args
}
避免零参数的特殊情况作为重载?
foo
可以避免零参数重载,有利于将last_t
特征调整为也接受空包的特征,以便使用对空包的查询来解析通用重载。然而,它的语义和实现都没有变得直接和优雅,因为“空类型列表中的最后一个类型”没有多大意义,这意味着需要将 trait 调整为不同的东西:
template <typename... Ts> struct last_or_unique_dummy_type {
using type = typename decltype((std::type_identity<Ts>{}, ...))::type;
};
template <> class last_or_unique_dummy_type<> {
struct dummy {};
public:
using type = dummy;
};
template <typename... Ts>
using last_or_unique_dummy_type_t =
typename last_or_unique_dummy_type<Ts...>::type;
template <typename... ARGS_,
std::enable_if_t<!std::is_same_v<
last_or_unique_dummy_type_t<ARGS_...>, Test *>> * = nullptr>
void foo(void (*fn)(ARGS_...)) {
std::cout << "Generic function pointer foo" << std::endl;
}
template <typename... ARGS_,
std::enable_if_t<std::is_same_v<last_or_unique_dummy_type_t<ARGS_...>,
Test *>> * = nullptr>
void foo(void (*fn)(ARGS_...)) {
std::cout << "Test function pointer foo" << std::endl;
}
对空包使用额外的重载可能是最不令人惊讶的方法。
C++20 和 identity_t 技巧
如果您还没有使用 C++20,那么您自己编写一个身份元函数是微不足道的:
template <typename T>
struct type_identity {
using type = T;
};
这个类模板的任何特化,除非部分/明确地特化(对于 STL 类型是 UB),都是微不足道的和默认可构造的。我们在last_t
上面的定义中利用了这一点:在未评估的上下文中默认构造一系列平凡类型,并利用这些类型中的最后一个将输入嵌入到其特化为该平凡类型的身份特征中,其包装的别名声明type
为可变参数包中最后一个参数的类型。
推荐阅读
- c++ - 如何在 C++ STL 的集合中找到有序的后继或前驱
- r - 从 IC2 函数中提取数字
- regex - 正则表达式:我怎样才能匹配第一个
- shell - 如何删除名称中包含 Ctrl-M 的文件?
- r - 如何使用在 r 中重复的另一个数据框中的特定列更新数据框中的新列?
- cassandra - 在 Cassandra 中,与压缩期间的行/单元墓碑相比,分区墓碑本质上是否更便宜?
- java - Mapbox轨迹视图Android
- visual-studio-code - 完全删除 vscode 并重新安装为默认值
- python - 在给定 SO URL 的情况下,如何获取特定问题、其答案和评论?
- c++ - 向量
转换为字符串并返回