37

I was wondering if I could have parameter packs consisting of a single, explicitly specified, type. For example, something like this:

#include <iostream>

using namespace std;

void show() { }

template<typename First, typename... Rest>
void show(First f, Rest... rest)
{
    cout << f << endl;
    show(rest...);
}

void foo(int f, int... args) // error
{
    show(f, args...);
}

int main()
{
    foo(1, 2, 3);
}

The problem I'm having is with the definition of foo(). With OS X clang++ version 5 (llvm 3.3svn) I get the error error: type 'int' of function parameter pack does not contain any unexpanded parameter packs.

Of course, I can get it to compile by changing to foo() into a function template:

template<typename... Args>
void foo(int f, Args... args)
{
    show(f, args...);
}

However now foo() will accept int for the first parameter, and anything output streamable for the rest. For example:

struct x { };
ostream& operator<<(ostream& o, x)
{
    o << "x";
    return o;
}

int main()
{
    foo(1, 2, x(), 3); // compiles :(
}

Now, I've seen the accepted solution here which suggests using type traits and std::enable_if, but that's cumbersome. They also suggested using std::array but I think a simple std::initializer_list works just fine and looks cleaner, like so:

void foo_impl(initializer_list<int> ints)
{
    for(int i: ints)
        cout << i << endl;
}

template<typename... Args>
void foo(int f, Args... args)
{
    foo_impl({f, args...});
}

struct x { };
ostream& operator<<(ostream& o, x)
{
    o << "x";
    return o;
}

int main()
{
    foo(1, 2, 3);
    foo(1, 2, x(), 3); // no longer compiles
                       // we also get an error saying no known conversion from 'x' to 'int' :)
}

So that's neat. But the question remains, is this necessary? Is there really not a way to define a non-template function which accepts a parameter pack of specific type? Like this:

void foo(int... args) { }
Community
  • 1
  • 1
stack_lexi
  • 373
  • 1
  • 3
  • 5
  • No. `int...` doesn't exist. The closest to this is, as you know, is `std::initializer_list`. But I agree that, perhaps, having `int...` rather than `std::initializer_list` could be nice. However, it's difficult to know if this wouldn't create bad side effects that `std::initializer_list` doesn't have. – Cassio Neri Dec 05 '13 at 18:31
  • 1
    @lucas92 the question is tagged c++11, he does not want to use C libraries. –  Dec 05 '13 at 18:59
  • 2
    The reason it must be a template is because each time the function is called with a differnet number of arguments, the compiler must generate the code for a function taking that specific number of arguments. – Oktalist Dec 05 '13 at 19:21
  • Are C++14/C++1y features acceptable? Because C++14 makes this *easy*. – Yakk - Adam Nevraumont Dec 05 '13 at 19:26
  • @Yakk: Using concept? or `auto`? or both? ;-) – Nawaz Dec 05 '13 at 19:31
  • 1
    @Nawaz Concepts lite I'd say. `template< ConvertsToInt... Ints > void foo(Ints...&& ints)` where `ConvertsToInt` is a type constraint function. I'd have to poke around with a C++14 compiler to produce a reliable answer, but... (I'd also have to check that `template` does not constrains `Ts` to be all the same type -- I remember a discussion about that, forgot what the conclusion was) – Yakk - Adam Nevraumont Dec 05 '13 at 19:37
  • @Yakk: Using concept-lite you could just write this : `void foo(ConvertsToInt ... ints) {}`. No need to write `template<...blah>` stuff. It is so cool :D – Nawaz Dec 05 '13 at 19:41
  • @Yakk: I added it in my answer for just completeness sake. – Nawaz Dec 05 '13 at 19:45
  • The enable_if solution is shorter and clearer than your initializer list with an extra helper function. And it allows more idiomatic looking use in client code. I personally wouldn't call that cumbersome. – Tim Seguine Dec 05 '13 at 20:14

6 Answers6

28
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!

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • I would also point out, for the sake of argument, that you could use `is_all_same` in an SFINAE fashion to remove `foo` from the overload set when the condition is not matched, instead of having it eagerly "capture" all calls just to fail. – Matthieu M. Dec 05 '13 at 18:46
  • @MatthieuM.: That may be good or bad, depending on what you want to do. An advantage with `static_assert` is that you can print good error messages. Of course, this doesn't fit if you want to other overloaded to be selected in case this `foo` isn't intended, in which case SFINAE would help. – Nawaz Dec 05 '13 at 18:53
  • 1
    Your `Integer` may be not quite right: `f( 'a', 0x0l )` would work with a function `f( int, int )`, but would fail with your `f( Integer... ints )`. Hence, `f( ConvertsToInteger...&& ints )` where you `return std::is_convertible< T, int >::value;` rather than `is_same`. This emulates normal function resolution "convert 1 step" in the call that `template` functions do not do. – Yakk - Adam Nevraumont Dec 05 '13 at 19:50
  • @Yakk: It all depends on how you define `Integer` of course. In this case, I show how to restraint it to only `int`. If you want to include `char` also, then you've to write the concept accordingly, but I think once you know the basic, you can write those variations yourself. – Nawaz Dec 05 '13 at 19:53
  • @Yakk: Added another concept called `Integral` which captures all integral types only, excluding the floating points. – Nawaz Dec 05 '13 at 19:58
  • 1
    @Nawaz I would aim for "anything that would work if your arguments where actually `int...`", which would be anything convertible-to-`int` implicitly. It isn't quite perfect, because your overload will be overly "greedy", but... – Yakk - Adam Nevraumont Dec 06 '13 at 04:50
  • @Yakk: Actually you cannot say `int` should work for `double` also, because there might be another overloaded function accepting only `double`. So it is up to the OP how he/she defines the concept depending on how he/she wants it to work. I cannot capture what he/she eventually needs, so I only demonstrated the basic idea, not a production-level code. – Nawaz Dec 06 '13 at 06:22
  • int... and Ints... are same only if you you pass to function real integers, but what about implicit conversions? And I am not talking about classes that can be constructed like this {1, "str"}. – jenkas Jan 06 '20 at 20:04
8

As with Brian's answer, I realize this was originally intended for C++11, but in C++20 this can be solved in a very simple way using concepts:

#include <concepts>

void f(std::integral auto... ints)
{
    // ...
}

std::integral accepts any integral type, so it's a bit more general, if that is acceptable. If not, you can do something like the following:

#include <concepts>

template<class T>
concept exactly_int = std::same_as<int,T>;

void f(exactly_int auto... ints)
{
   // ...
}

To add a bit more explanation to this, the auto is essentially an implicit template, and the name before it is constraining what types are allowed. So in the first example, anything that satisfies std::integral (int,long,unsigned,char, etc.) will be allowed. The second allows only ints, since that is the only type that satisfies the concept that was defined.

There is an even simpler way to do this: Concepts when used as constrains use the type that is being constrained as its first argument, so you can simply write:

#include <concepts>

void f(std::same_as<int> auto... ints)
{
   // ...
}
Alex Trotta
  • 81
  • 1
  • 3
3

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.

xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 1
    Because that would require `foo({1, 2, 3})` and the intent was to support the interface `foo(1, 2, 3)`. However, I suppose you could just rename `foo_impl()` to `foo()` to support both call styles at the same time, which might be best anyway. – stack_lexi Dec 05 '13 at 21:27
2

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

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', ...);
}
Brian Rodriguez
  • 4,250
  • 1
  • 16
  • 37
-1

You can't use a template like

<MyType i, MyType ... myTypes>

for function, but you cat use it for class/struct. Just change your function to a static struct member and it's done (sum example):

 template<int i, int ... ints>
 struct A <i, ints...> {
    static int Do() {
        return i + A<ints...>::Do();
    }
 };

This is not all job to do - a boundary struct is also needed. All the example (with Show() method):

#include <iostream>

using namespace std;

template<int ...> // declaration needed
struct A {};

template<int i>   // boundary
struct A <i>{
    static void Show() {
        cout << i << endl;
    }
};

template<int i, int ... ints> // main struct
struct A <i, ints...> {
    static void Show() {
        cout << i << endl;
        A<ints...>::Show();
    }
};

int main() {
    A<5, 45, 2, -100>::Show();
    cout << endl;
    A<-15, -3, 2>::Show();
    return 0;
}
Sylwester
  • 11
  • 2