2

I can't figure out how to implement a function with a variable number of arguments of the same type.

I'm writing for a microcontroller with little stack and memory, so I can't use recursion or the STL (the parts with exceptions).

Is it possible to make such a function?

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

which expands to something like this:

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

example of invocation:

S s;
const int mode=3, speed=1;

fun<&s,1,2,7,4>(mode,speed);
daniil me
  • 31
  • 4

5 Answers5

2

Having all parameters in a variadic pack be of same type can be required with C++20 concepts.

Unfortunately, as of C++20, the standard library doesn't have a concept for all_same (there is only std::same_as for two types), but it can be easily defined:

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) {}

Code: https://godbolt.org/z/dH9t-N


Note that in many cases requiring same type is not necessary, you may instead require that all arguments have a common type. To require common type, based on test if common type exists you can have the following concept:

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) {}

Code: https://godbolt.org/z/5M6dLp

Amir Kirsh
  • 12,564
  • 41
  • 74
2

You can easily do this with fold expression (c++17) and concepts (c++20) features.

The concept will look like this:

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

If you want them to be just the same type, you can use it this way:

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

If you want a function to take a specific type, you can use it this way:

template<is_all_same<int>... Types>
void fun();
Bioliquid
  • 161
  • 7
1

Unfortunately, there is currently no way to specify a function parameter pack where each parameter is of the same type. The next best thing you can get (as far as I'm aware) would be a function that takes a variable number of arguments of any type as long as the type of all of them is 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
}

live example here

The trick here is to rely on SFINAE to effectively remove all versions of the function where not all the args end up being of type int

For your concrete example, you could do, e.g.:

#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);
}

live demo here

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
1

I can't figure out how to implement a function with a variable number of arguments of the same type.

Template argument of the same type or ordinary function arguments of the same type?

The first case is simple (if the type is one admitted for template value types), exactly as you have written

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

and you can use they using template folding, if you can use C++17,

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

or in a little more complicated way before (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 ... };
 }

Unfortunately you can call this type of function with compile time known integers so, by example, not with variables

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

In this case you need a variadic list of ordinary function arguments of the same type; unfortunately this is a little more complicated.

You can create a typical variadic list of template parameter

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

imposing, through SFINAE, that all Args... are deduced or explicated as int (see Michael Kenzel's answer).

Unfortunately this require that every argument is exactly if type int so calling func with (by example) a long int gives a compilation error

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

Obviously you can relax the SFINAE condition imposing (by example) that all Args... types are convertible (std::is_convertible) to int but isn't exactly has developing a function receiving a variadic number of arguments of the same type.

If you can accept a superior limit to the number of arguments (64, in the following example) and that the function is method (maybe static) of a class, you can create a foo class containing a method f() that receive zero int, one f() that receive one int, one f() that receive two ints, etc, until an f() that receive 63 ints.

The following is a full compiling C++17 example

#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);
 }

In C++14 is a little more complicated because there isn't variadic using so you have to write the foo class in a recursive way.

In C++11 you have also to develop a substitute for std::make_index_sequence/std::index_sequence.

max66
  • 65,235
  • 10
  • 71
  • 111
0

More expanded example with multiple arguments.

From this answer here > Is there a way to define a variadic number of arguments of the same type?

Shorter initialization (std::is_same_v) and example of using >

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;
}

And some usable examples

// 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);
}

And consequence at all for me - better use "init list" - there we see size() function - access to amount of arguments

const std::initializer_list<const char*> &args
Redee
  • 547
  • 5
  • 8