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

14ifxucb  于 2023-02-20  发布在  其他
关注(0)|答案(5)|浏览(107)

我不知道如何实现一个具有可变数量的相同类型参数的函数。
我写的微控制器有很少的堆栈和内存,所以我不能使用递归或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);
j13ufse2

j13ufse21#

在C20概念中,可变参数包中的所有参数都是相同类型可能是***必需的***。
不幸的是,在C
20中,the standard library doesn't have a concept for 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

注意在许多情况下,要求相同类型不是必需的,您可以改为要求所有参数具有***公共类型***。要基于 test if common type exists 要求公共类型,您可以具有以下概念:

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

jjjwad0x

jjjwad0x2#

你可以通过fold expression(c17)和concepts(c20)特性轻松地做到这一点。
其概念如下所示:

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();
gcxthw6b

gcxthw6b3#

我不知道如何实现一个具有可变数量的相同类型参数的函数。
相同类型的模板参数还是相同类型的普通函数参数?
第一种情况很简单(如果类型是模板值类型所允许的类型),与您编写的完全相同

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类型,因此使用(例如)long int调用func会产生编译错误

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

显然,您可以放宽SFINAE条件,例如,所有Args...类型都可以转换(std::is_convertible)为int,但这并不完全需要开发一个接收可变数量的相同类型参数的函数。
如果可以接受参数数量的上限(在下面的示例中为64),并且函数是method(可能是静态),您可以创建一个foo类,该类包含一个接收零个int的方法f()、一个接收一个intf()一个f()接收两个int,等等,直到一个f()接收63个int
下面是一个完整的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);
 }

在C14中要稍微复杂一点,因为没有可变变量using,所以你必须用递归的方式写foo类。
在C
11中,你还必须开发std::make_index_sequence/std::index_sequence的替代。

6tqwzwtp

6tqwzwtp4#

不幸的是,目前还没有办法指定一个每个参数都是相同类型的函数参数包,你能得到的下一个最好的东西(据我所知)是一个函数,它可以接受任意类型的可变数量的参数,只要它们的类型都是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);
}

此处为现场演示

q8l4jmvw

q8l4jmvw5#

包含多个参数的更详细的示例。
从这里的答案〉有没有一种方法可以定义相同类型的变元数参数?
更短的初始化(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);
}

对我来说,最好使用“init list”,我们可以看到size()函数,可以访问大量参数

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

相关问题