C++11 Variable Number of Arguments, Same Specific Type

C++11 variable number of arguments, same specific type

A possible solution is to make the parameter type a container that can be initialized by a brace initializer list, such as std::initializer_list<int> or std::vector<int>. For example:

#include <iostream>
#include <initializer_list>

void func(std::initializer_list<int> a_args)
{
for (auto i: a_args) std::cout << i << '\n';
}

int main()
{
func({4, 7});
func({4, 7, 12, 14});
}

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

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.

Can I create a function which takes any number of arguments of the same type?

Use C++17 fold expression:

template<class... Args>
constexpr auto total_sum(const Args&... args) {
return (args + ... + 0);
}

static_assert(total_sum(1, 2, 5, 4, 2) == 14);
static_assert(total_sum(3, 5, 6, 2) == 16);

variable number of arguments, same specific type without macro or initializer-list

{} requires you are initializing something of a specific type.

C++11 variable arguments requires that your types be deducted types.

These are opposing requirements.

Now I could generate an object with a set of () overloads up to some large finite number.

namespace details {
template<std::size_t, class T>
using ignore_index=T;

template<class T, class Count, class Base>
struct linear_overload_count;
template<class T, std::size_t I0, std::size_t...Is, class Base>
struct linear_overload_count<T, std::index_sequence<I0,Is...>, Base>:
linear_overload_count<T, std::index_sequence<Is...>, Base>
{
using linear_overload_count<T, std::index_sequence<Is...>, Base>::operator();
using linear_overload_count<T, std::index_sequence<Is...>, Base>::linear_overload_count;

std::result_of_t<
Base const&(T const&, ignore_index<Is,T>const&...)
>
operator()(T const& t0, ignore_index<Is,T>const&...ts) const {
return Base::operator()(t0, ts...);
}
linear_overload_count()=default;
linear_overload_count(linear_overload_count const&)=default;
linear_overload_count(linear_overload_count &&)=default;
linear_overload_count& operator=(linear_overload_count const&)=default;
linear_overload_count& operator=(linear_overload_count &&)=default;
};
template<class T, class Base>
struct linear_overload_count<T, std::index_sequence<>, Base>:
Base
{
using Base::Base;
linear_overload_count(Base&& b):Base(std::move(b)) {}
linear_overload_count(Base const& b):Base(b) {}
std::result_of_t<
Base const&()
>
operator()() const {
return Base::operator()();
}
linear_overload_count()=default;
linear_overload_count(linear_overload_count const&)=default;
linear_overload_count(linear_overload_count &&)=default;
linear_overload_count& operator=(linear_overload_count const&)=default;
linear_overload_count& operator=(linear_overload_count &&)=default;
};
}
template<class T, std::size_t N, class Base>
using linear_overload_Ts = details::linear_overload_count<T, std::make_index_sequence<N>, Base>;

auto count_args_impl = [](auto&&...args) { std::cout << sizeof...(args) << "\n"; };

struct bob {
int x,y;
};

using count_bobs_t = linear_overload_Ts< bob, 3, decltype(count_args_impl) >;
count_bobs_t const bobs = count_args_impl;

int main() {
bobs();
bobs({}, {}, {1,2});
}

live example.

Now we can have up to 100 overloads in bobs by changing the number 3 above to 100.

Note that if you hit a more than a few 100, your compiler will die. This can be fixed with a binary tree inheritance instead of a linear one, but I cannot be bothered.

In addition, this technique can slow down compiling.

Note that Base must be a type. You can use a lambda like above to forward to your template function (give them different names), a manual function object, or whatever else.

Using this technique to create a function instead of a function object isn't something I can solve without the help of a type being named in the call (so, use ADL to find the generated function). Function objects don't participate in overload resolution in the same way that functions do, which may be a problem.

This also seems like a lot of work to do away with adding an extra set of {}.

A function with variable number of arguments with known types, the c++11 way

It is straight forward to write a function with variadic templates, that accept an arbitrary number of arguments. The only difference to the general pattern is, that a concrete type is used as first argument (head) - instead of a template parameter. The following example shows a function foobar, that accepts an arbitrary number of strings.

// used for end of recursion - and for the empty arguments list
void foobar() { }

template <typename ...Tail>
void foobar(const std::string& head, Tail&&... tail)
{
// do something with head
std::cout << head << '\n';
// call foobar recursively with remaining arguments
foobar(std::forward<Tail>(tail)...);
}

foobar("Hello", "World", "...");

Personally, I prefer using std::initializer_list instead of variadic templates. Because variadic templates are more complex and require additional experience. With std::initializer_list, it might look like this:

void foobar(std::initializer_list<std::string> values)
{
for (auto& value : values) {
// do something with value
std::cout << value << '\n';
}
}

foobar({ "Hello", "World", "...", });

Unfortunately, the additional curly braces are required when using std::initializer_list with regular functions. They are not required for constructors, if the new initializer syntax is used.

Edit: Rewrote the answer according to the feedback. In particular I have changed the order of the two solutions/examples.

Using a function with a variable number of arguments

You can use variadic templates. One example for printing out all the passed argument is given below:

#include <iostream>
//provide an ordinary function to end recursion
void print ()
{
}

template<typename T, typename... Types>
void print (T firstArg, Types... args)
{
std::cout << firstArg << "\n"; // printing the very first argument passed
print(args...); // printing the rest of the argument by calling print()
}

int main()
{
print(1, 2, 3, "some string literal");//call function template print() with arguments `1`, `2`, `3` and `"some string literal"`
return 0;
}

In the above snippet, if we call print() with one or more arguments then the templated version of print will be used/called which just prints the very first argument passed(which is 1 in my example) using cout and then calls print with the remaining arguments(which are 2, 3 and "some string literal.

Now the whole process repeats. In particular, the very first argument 2 is printed using cout and then print is called with the remaining arguments(3, "some string literal").

This goes on until there are no more arguments to pass and in this case when print is called with no arguments then the ordinary non-template function print will be used/chosen, thus ending the recursion.

The output of the above program can be seen here:

1
2
3
some string literal

Note that there are other ways as well like using fold expression(with C++17) to do the same. Also, in the above example, the arguments are passed by value. You can modify the program to pass them by reference according to your needs.

With C++17, you can use fold expression:

#include <iostream>
template<class... Args>
void print(const Args&... args)
{
(std::cout << ... << args) << "\n"; //uses fold expression
}

int main()
{
print(1, 2, 3, "some string literal");
return 0;
}

is there a c++ version of int..args

From C++11 and above, you can use parameter packs and store them in a std::initializer_list:

#include <iostream>
#include <initializer_list>

template <typename ...Args>
void fun(Args const&... args) {
std::initializer_list<int> arg_list { args... };
std::cout << "Number of arguments: " << arg_list.size() << std::endl;

for (auto const& i : arg_list)
std::cout << i << " ";
std::cout << std::endl;
}

int main() {
// Calling the varargs method with different number
// of parameters
fun(100); // one parameter
fun(1, 2, 3, 4); // four parameters
fun(); // no parameter
// fun(1, ""); // Error
}


Related Topics



Leave a reply



Submit