我试图弄清楚如何使用“更专业”的可变参数函数模板“重载”可变参数函数模板。例如:
#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;
}
Run Code Online (Sandbox Code Playgroud)
这段代码的输出是:
Generic function pointer foo
Generic function pointer foo
Run Code Online (Sandbox Code Playgroud)
而不是:
Generic function pointer foo
Test function pointer foo
Run Code Online (Sandbox Code Playgroud)
如我所愿。
从概念上讲,我试图指出“如果您有任何类型的参数,其中最后一个是 Test*,则使用模板方法 A,如果最后一个类型不是 Test*,则使用模板方法 B。”
完成这种行为的正确方法是什么?
您可以根据可变参数包中的最后一个类型是否添加互斥重载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; }
Run Code Online (Sandbox Code Playgroud)
将 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
}
Run Code Online (Sandbox Code Playgroud)
foo
可以避免零参数重载,有利于将last_t
特征调整为一个也接受空包的特征,以便对空包的查询用于解析通用重载。然而,它的语义和实现都没有变得那么直接和优雅,因为“空类型列表中的最后一个类型”没有多大意义,这意味着需要将特征调整为不同的东西:
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;
}
Run Code Online (Sandbox Code Playgroud)
对空包使用额外的重载可能是最不令人惊讶的方法。
如果您还没有使用 C++20,那么自己编写一个标识元函数是微不足道的:
template <typename T>
struct type_identity {
using type = T;
};
Run Code Online (Sandbox Code Playgroud)
这个类模板的任何特化,除非部分/显式特化(这是 STL 类型的 UB),都是微不足道的和默认构造的。我们在last_t
上面的定义中利用了这一点:在未计算的上下文中默认构造一系列平凡类型,并利用这些类型中的最后一个将输入嵌入到身份特征中,其特化是那个平凡类型,并且其包装别名声明type
是可变参数包中最后一个参数的类型。
归档时间: |
|
查看次数: |
93 次 |
最近记录: |