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

Is there a way to define a variadic number of arguments of the same type?

本文关键字:参数 数数 变参 同类型 定义 有没有      更新时间:2023-10-16

我不知道如何实现具有相同类型参数的可变数量的函数。

我正在为堆栈和内存很少的微控制器编写,所以我不能使用递归或 STL(有例外的部分)。

有没有可能做出这样的功能?

struct S{
int r1;
int r2;
};
template<S* s, int... args> fun(int arg1, int arg2);

它扩展到这样的内容:

for(int arg:args){ 
s->r1+=7*arg;
}

调用示例:

S s;
const int mode=3, speed=1;
fun<&s,1,2,7,4>(mode,speed);

对于C++20 概念,可能需要让可变参数包中的所有参数具有相同的类型。

不幸的是,截至 C++20 年,标准库没有all_same的概念(只有两种类型的std::same_as),但它可以很容易地定义:

template<class... Ts>
concept all_same = 
sizeof...(Ts) < 2 ||
std::conjunction_v<
std::is_same<std::tuple_element_t<0, std::tuple<Ts...>>, Ts>...
>;
template<typename... Ts> requires all_same<Ts...>
void foo(Ts&&... ts) {}

代码:https://godbolt.org/z/dH9t-N


请注意,在许多情况下,不需要相同的类型,而是要求所有参数都具有通用类型。若要要求通用类型,根据测试是否存在通用类型,可以具有以下概念:

template <typename AlwaysVoid, typename... Ts>
struct has_common_type_impl : std::false_type {};
template <typename... Ts>
struct has_common_type_impl<std::void_t<std::common_type_t<Ts...>>, Ts...>
: std::true_type {};
template <typename... Ts>
concept has_common_type = 
sizeof...(Ts) < 2 ||
has_common_type_impl<void, Ts...>::value;
template<typename... Ts> requires has_common_type<Ts...>
void foo(Ts&&... ts) {}

代码:https://godbolt.org/z/5M6dLp

您可以使用折叠表达式(c++17) 和概念(c++20) 功能轻松完成此操作。

概念将如下所示:

template<typename T, typename... Types>
concept is_all_same = (... && std::is_same<T, Types>::value);

如果您希望它们只是相同的类型,则可以通过以下方式使用它:

template<typename... Types> requires is_all_same<Types...>
void fun();

如果希望函数采用特定类型,可以通过以下方式使用它:

template<is_all_same<int>... Types>
void fun();

我不知道如何实现具有相同类型参数的可变数量的函数。

相同类型的模板参数还是相同类型的普通函数参数?

第一种情况很简单(如果该类型是模板值类型的允许类型),与您编写的完全一样

template<S* s, int... args>
fun (int arg1, int arg2);

你可以使用模板折叠来使用它们,如果你可以使用C++17,

template <S* s, int... args>
auto fun (int arg1, int arg2)
{ ((s->r1 += 7 * args), ...); }

或者之前以更复杂的方式(C++11/C++14)

template <S* s, int... args>
auto fun (int arg1, int arg2)
{ 
using unused = int[];
(void)unused { 0, s->r1 += 7 * args ... };
}

不幸的是,您可以使用编译时已知的整数调用这种类型的函数,因此,例如,不使用变量

int a = 7;
fun<&s,1,2,a,4>(mode,speed); // compilation error

在这种情况下,您需要相同类型的普通函数参数的可变参数列表;不幸的是,这有点复杂。

您可以创建模板参数的典型可变参数列表

template <typename ... Args>
auto fun (Args ... args)

通过SFINAE,强加所有Args...都被推导或解释为int(见迈克尔·肯泽尔的回答)。

不幸的是,这要求每个参数都是如果类型int,因此使用(通过示例)调用 funclong int会给出编译错误

fun(1, 2, 3l); // compilation error (3l is a long int, not an int)

显然,你可以放宽SFINAE条件,强加(通过示例)所有Args...类型都可以转换为(std::is_convertible)到int,但并不完全开发一个函数接收相同类型的可变参数数。

如果你可以接受参数数量的上限(在下面的示例中是64),并且该函数是一个类的方法(可能是静态的),你可以创建一个foo类,其中包含一个接收零int的方法f(),一个接收一个intf(),一个接收两个intf(), 等等,直到收到 63int秒的f()

以下是完整的编译C++17示例

#include <utility>
#include <type_traits>
struct S
{
int r1;
int r2;
};
S s;
const int mode=3, speed=1;
template <typename T, std::size_t>
using getType = T;
template <std::size_t N, typename = std::make_index_sequence<N>>
struct bar;
template <std::size_t N, std::size_t ... Is>
struct bar<N, std::index_sequence<Is...>>
{
static constexpr auto f (getType<int, Is> ... args)
{ ((s.r1 += 7 * args), ...); }
};
template <S &, std::size_t N = 64u, typename = std::make_index_sequence<N>>
struct foo;
template <S & s, std::size_t N, std::size_t ... Is>
struct foo<s, N, std::index_sequence<Is...>> : public bar<Is>...
{ using bar<Is>::f...; };
int main ()
{
foo<s>::f(mode, speed);
}

在 C++14 中稍微复杂一些,因为没有可变参数using所以你必须以递归的方式编写foo类。

在 C++11 中,您还必须开发std::make_index_sequence/std::index_sequence的替代品。

遗憾的是,目前无法指定每个参数属于相同类型的函数参数包。您可以得到的下一个最好的东西(据我所知)是一个函数,它接受可变数量的任何类型的参数,只要它们的类型都int

#include <type_traits>
template <typename... Args>
auto f(Args... args) -> std::enable_if_t<(std::is_same_v<Args, int> && ...)>
{
…
}
void test()
{
f(1, 2, 3);     // OK
f(1, 2, 3.0f);  // error
}

现场示例在这里

这里的诀窍是依靠 SFINAE 有效地删除函数的所有版本,其中并非所有args最终都是int型......

对于您的具体示例,您可以这样做,例如:

#include <type_traits>
struct S
{
int r1;
int r2;
};
template <S& s, typename... Args>
auto f(Args... args) -> std::enable_if_t<(std::is_same_v<Args, int> && ...)>
{
((s.r1 += 7 * args), ...);
}
S s;
const int mode=3, speed=1;
void test()
{
f<s>(mode, speed);
}

现场演示在这里

具有多个参数的更多扩展示例。

从这个答案这里>有没有办法定义相同类型的参数的可变参数数?

较短的初始化 (std::is_same_v) 和使用>的示例

template<class T, class... Types>
concept is_all_same = (... && std::is_same_v<T, Types>);
// "requires" to prevent function instance without arguments
template<class... Types> requires is_all_same<Types...>
void fun1(const Types&... types)
{
for (const auto &t : {types...})
std::cout << t << std::endl;
}
template<is_all_same<int>... Types> requires is_all_same<Types...>
void fun2(const Types&... types)
{
for (const int &t : {types...})
std::cout << t << std::endl;
}

还有一些可用的例子

// check same type
template<class T, class... Types>
concept is_all_same = (... && std::is_same_v<T, Types>);
// different amount of arguments, different one type
template<class... Types> requires is_all_same<Types...>
void fun1(const Types&... types)
{
for (const auto &t : {types...})
std::cout << t << std::endl;
}
// different amount of arguments one type - const char*
template<is_all_same<const char*>... Types> requires is_all_same<Types...>
void fun2(const Types&... types)
{
for (const char *t : {types...})
std::cout << t << std::endl;
}
//
// check c-array of chars
template<class T, class... Types>
concept is_c_arr_char =
std::is_bounded_array_v<T> &&
std::is_same_v<std::remove_all_extents_t<T>, char> &&
(... && (
std::is_bounded_array_v<Types> &&
std::is_same_v<std::remove_all_extents_t<Types>, char>
));
// different amount of arguments
// different type based on "signature" c-array chars - const char[x]
template<class... Types> requires is_c_arr_char<Types...>
void fun3(const Types&... types)
{
for (const char *t : {types...})
std::cout << t << std::endl;
}
//
int main()
{
fun1(1.1, 2.2);

const char* a = "a1";
const char* b = "b22";
fun2(a, b);
fun2((const char*)"a1", (const char*)"b22");

fun3("c3", "d44");
const char c[] = "c3";
const char d[] = "d44";
fun3(c, d);
}

对我来说后果 - 最好使用"初始化列表" - 在那里我们看到 size() 函数 - 访问参数数量

const std::initializer_list<const char*> &args