基于最后一个参数包参数的 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++20 的 std::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是可变参数包中最后一个参数的类型。