C++ 中带有类型检查的可变参数模板

问题描述 投票:0回答:3

我需要一个驱动程序接口,仅提供以下类型的一组 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)'
}

这可能吗?

c++ overloading variadic-templates
3个回答
1
投票

尝试这样的事情:

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)
。如果您想避免这种情况,可以使用额外的模板技巧来确保所有可变参数都是同一类型,请参阅:

为传递给可变参数函数或可变参数模板函数的所有参数指定一种类型,不使用数组、向量、结构体等?

有没有办法定义相同类型的可变数量的参数?


1
投票

是的,可以用 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)
}

演示

但是重载更简单、更清晰。

此外,作为模板,您必须在标头中提供实现,(或使用显式实例化......但在这种情况下,常规重载又更简单)。


0
投票

这在 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
处理该功能。

© www.soinside.com 2019 - 2024. All rights reserved.