我需要一个驱动程序接口,仅提供以下类型的一组 send() 函数:
void send(u8 a);
void send(u8 a, u8 b);
void send(u8 a, u8 b, u8 c);
void send(u8 *a, u32 size);
我想缩短这个界面的外观并使用变量模板来实现它。 可以看到,首先需要检查第一个参数是否是指针,如果是,则单独处理。在其他情况下,您必须防止输入超过 3 个 u8(无符号字符)类型的参数。
我对enable_if、SFINAE等的使用完全感到困惑
我的例子:
template<typename ...Args>
void send(Args... args) {
}
void send(u8 *p, u32 size) {
}
int main() {
u8 b;
send(1); // OK
send(1, 2); // OK
send(1, 2, 3); // OK
send('1', '2', 3, 4); // NOT OK: 4 arguments were passed, I need a maximum of 3
send(&b, 10); // NOT OK: call template 'send', not specialized 'send(u8 *p, u32 size)'
}
这可能吗?
尝试这样的事情:
void send() {} // do nothing
void send(u8 *a, u32 size) {
...
}
template<typename... Args>
void send(u8 arg, Args... args) {
static_assert(sizeof...(Args) <= 2, "No more than 3 args allowed!");
// use arg as needed, then...
send(args...); // send the rest...
}
或者:
void send(u8 *a, u32 size) {
...
}
template<typename... Args>
void send(u8 arg, Args... args) {
static_assert(sizeof...(Args) <= 2, "No more than 3 args allowed!");
// use arg as needed, then...
if constexpr (sizeof...(Args) > 0) {
send(args...); // send the rest...
}
}
但是,这种方法的一个问题是像
send(1, &b, 10);
这样的东西会被接受。它将处理 arg=1
,然后调用 send(&b, 10)
。如果您想避免这种情况,可以使用额外的模板技巧来确保所有可变参数都是同一类型,请参阅:
是的,可以用 SFINAE/概念来表达限制,例如:
template <typename ...Args>
void send(Args...)
requires (
(sizeof...(Args) == 1 && (std::is_convertible_v<Args, u8> && ...))
|| (sizeof...(Args) == 2
&& ((std::is_convertible_v<Args, u8> && ...)
|| (std::is_convertible_v<std::tuple_element_t<0, std::tuple<Args...>>, u8*> &&
std::is_convertible_v<std::tuple_element_t<1, std::tuple<Args...>>, u32>)
)
)
|| (sizeof...(Args) == 3 && (std::is_convertible_v<Args, u8> && ...))
)
{
// if constexpr (dispatch on above conditions)
}
但是重载更简单、更清晰。
此外,作为模板,您必须在标头中提供实现,(或使用显式实例化......但在这种情况下,常规重载又更简单)。
这在 C++20 中很容易做到。 您可以按原样保留采用指针的重载,并添加带有一些约束的可变参数模板来处理其他所有内容:
#include <concepts>
// type-constraint for each template argument
template <std::convertible_to<u8> ...Args>
// and also a requires-clause that limits the amount of arguments
requires (sizeof...(Args) != 0 && sizeof...(Args) <= 3)
void send(Args... args) {
// ...
}
void send(u8 *p, u32 size) {
// ...
}
如果您使用的是 C++17,您可以通过使用
std::enable_if
和折叠表达式来完成同样的操作,但它不会那么漂亮。
这就是我实际设计这个界面的方式。 有一个单一的函数
std::span
:
void send(std::span<const u8> data);
std::array<u8, 2> a{1, 2};
send(a);
您通常应该更喜欢使用
std::span
而不是指针和大小的重载。
如果你有这样的重载,你也可以让用户将他们的数据放入临时数组中,而不是让 send
处理该功能。