I was wondering if I could have parameter packs consisting of a single, explicitly specified, type. For example, something like this:
#include
Why the foo_impl
workaround, and not just use initialize_list<int>
in foo
's signature directly? It clarifies that you accept a variable-size argument list of said type.
You might specify the type you want to show:
#include <iostream>
template<typename T>
void show_type() {}
template<typename T, typename... Rest>
void show_type(const T& x, Rest... rest)
{
std::cout << x << std::endl;
show_type<T>(rest...);
}
template<typename... Args>
void foo(int x, Args... args)
{
show_type<int>(x, args...);
}
struct X { };
std::ostream& operator<<(std::ostream& o, X)
{
o << "x";
return o;
}
int main()
{
foo(1, 2, 3);
foo(1, 2, 3.0); // Implicit conversion
// or just
show_type<int>(1, 2, 3);
foo(1, 2, X()); // Fails to compile
}
I realize this is tagged C++11, but the features of C++17/1z works fantastically here so I figured its solution is worth posting:
template<typename... Ints>
void foo(Ints... xs)
{
static_assert(std::conjunction<std::is_integral<Ints>...>::value);
(std::cout << xs << '\n', ...);
}
void foo(int... args) {}
No you cannot write that.
But you can have the same effect with this approach:
template<typename ...Ints>
void foo(Ints... ints)
{
int args[] { ints... }; //unpack ints here
//use args
}
With this approach, you can pass all int
if you want. If any argument passed to foo
is not int
or convertible to int
, the above code will result in compilation error, as it would be the case with int ...args
approach if it were allowed.
You could also use static_assert
to ensure all Ints
are indeed int
if you want that behaviour:
template<typename ...Ints>
void foo(Ints... ints)
{
static_assert(is_all_same<int, Ints...>::value, "Arguments must be int.");
int args[] { ints... }; //unpack ints here
//use args
}
Now you've to implement is_all_same
meta-function which is not difficult to implement.
Alright, this is the basic idea. You can write more sophisticated code with variadic templates and with the help of some utility meta-functions and helper functions.
For lots of work that you can do with variadic arguments, you don't even need to store in args[]
array, e.g if you want to print the arguments to std::ostream
, then you could just do it as:
struct sink { template<typename ...T> sink(T && ... ) {} };
template<typename ...Ints>
void foo(Ints... ints)
{
//some code
sink { (std::cout << ints)... };
}
Here you create a temporary object of type sink
so that you use unpack the template arguments using list-initialization syntax.
Last you could just use std::initializer_list<int>
itself:
void foo(initializer_list<int> const & ints)
{
}
Or std::vector<int>
in case if you need vector-like behavior inside foo()
. If you use any of these, you have to use {}
when calling the function as:
f({1,2,3});
That may not be ideal but I think with the advent of C++11 you will see such code very frequently!