Concept for any noexcept invocable with any number of parameters - c++

I am trying to use a Concept to constrain a template parameter to only allow invocables that are noexcept.
E.g.:
template<NoExceptFunc Fn>
void foo(Fn invocable) noexcept {
invocable();
}
The difficulty I have is that the invocable should be any type of invocable (free function, lambda, ...) and allow any number of parameters.
EDIT: Thanks to How can unspecified types be used in C++20 'requires' expressions?, I understand this is not possible to do in the general case, you need to know what invocable actually is, and then can constraint that.
Still, even when I know what invocable actually is, I do not know how to express the contrain, see below.
The following
template <typename T, typename... Args>
concept NoExceptFunc = requires(T &&f, Args &&...args) { requires noexcept(f(args...)); };
works, but requires that the arguments types be explicitly given, e.g.:
template<NoExceptFunc<int, int> Fn>
void foo(Fn invocable) noexcept {
invocable(0, 1);
}
The problem is that I do not know how to specify the parameters in the following case:
template <NoExceptFunc<??> Fn>
void foo(Fn f) noexcept {
f(1);
f("hello", "world");
}
int main() {
auto lambda = [](auto&& a, auto&&... rest) {
std::cout << a;
if constexpr(sizeof...(rest) != 0) {
std::cout << (rest << ...);
}
std::cout << std::endl;
};
foo(lambda);
}
How can I say that the parameters can be <int> OR <const char*, const char*>?

Related

Specialization for variadic template

I want to write a function for any number of parameters,
inline void show_log(const char* s)
{
std::cout << s << std::endl;
}
And I write it as
https://en.cppreference.com/w/cpp/language/parameter_pack instructs.
inline void show_log(const char* s)
{
std::cout << s << std::endl;
}
template<typename T, typename... Targs>
inline void show_log(T s, Targs ... args)
{
show_log(s);
show_log(args...);
}
inline void test()
{
show_log("abc", "ttt", "ccc");
}
It works fine. But I want more strict – make it only accepts const char* parameters. I tried this:
Declare a general template function (but not implement it.)
template<typename T, typename... Targs>
inline void show_log(T s, Targs ... args);
Then implement a specilization only for const char*
template<const char*, typename... Targs>
inline void show_log(const char* s, Targs ... args)
{
std::cout<<"show_log spelicalized for const char*\n"
show_log(s);
show_log(args...);
}
Call the function,
inline void test()
{
show_log("abc", "ttt", "ccc");
}
It didn't compile with LNK2019 `show_log(const char*, const char*, const char*)' didn't implement error.
The linker error that you're seeing is caused by the fact that you're creating an overload, not a specialization. Functions must be fully specialized since they cannot be overloaded. Classes are allowed to be partially specialized since they cannot be overloaded.
That said, you need to impose a constraint to your template arguments to achieve this behavior.
First, you need to define a meta-function:
// Returns true if all types are the same
// Returns false if any types are different
// Fails to compile if 0 or 1 arguments are passed in
template<typename first_t, typename ... rest_t>
struct is_all_same : std::conjunction<std::is_same<first_t, rest_t>...> {};
template<typename ... types_t>
constexpr auto is_all_same_v = is_all_same<types_t...>::value;
And then you can use the meta function in a number of ways to restrict your function usage.
// Using a static assert
template<typename T, typename... Targs>
inline void show_log(T s, Targs ... args)
{
static_assert(is_all_same_v<const char*, T, Targs...>, "Arguments must be of type: const char*");
show_log(s);
show_log(args...);
}
// requires clause is only available in c++20
template<typename T, typename... Targs> requires is_all_same_v<const char*, T, Targs...>
inline void show_log(T s, Targs ... args)
{
show_log(s);
show_log(args...);
}
// Use SFINAE (do yourself a favor and use C++ requires instead of this if it's available to you)
template<typename T, typename... Targs>
inline std::enable_if_t<is_all_same_v<const char*, T, Targs...>> show_log(T s, Targs ... args)
{
show_log(s);
show_log(args...);
}
One thing to note, is that you appear to be using recursion to accomplish this. However, you do not need to use recursion here if you have C++17 available (which I assume you do, given the tag). I would recommend doing something like this for c++17:
inline void show_log(const char* s)
{
std::cout << s << std::endl;
}
// No recursion. Be nice to your compiler when you can
template<typename ... args_t>
inline auto show_log(args_t ... args)
{
static_assert(is_all_same_v<const char*, args_t...>, "Arguments to show_log must have the type: const char*");
(show_log(args), ...);
}
If you are going to be using variadics (and templates in general), I would highly recommend keeping the above meta function handy. I use it everywhere in my template code. Understand it and digest it, so you can add more meta functions like it to your tool belt.
Edit: As Keijo excellently points out below, you may actually be interested in the single-function implementation of your pattern. Combining all of the recommendations on this thread yields you with something like this:
// Returns true if all types are the same
// Returns false if any types are different
// Fails to compile if 0 or 1 arguments are passed in
template<typename first_t, typename ... rest_t>
struct is_all_same : std::conjunction<std::is_same<first_t, rest_t>...> {};
template<typename ... types_t>
constexpr auto is_all_same_v = is_all_same<types_t...>::value;
template<typename ... args_t>
auto show_log(args_t ... args) noexcept -> void
{
using wanted_type = const char*;
static_assert(is_all_same_v<wanted_type, args_t...>, "Arguments must be of type: const char*");
((std::cout << args << '\n'), ...) << std::flush;
}
This solution:
Is defined by a single variadic function
Is C++17 compatible
Throws a compile error if any of the arguments are not of type const char*
Allows you to reuse is_all_same_v<args_t...> wherever you see fit
Gives a useful compiler error
Avoids unnecessary flushes by using '\n' rather than std::endl
Removes the inline keyword, since this is a function template (which natively has the same semantics as an inline function and is therefore redundant... Note that this does not apply to full specializations of function templates, since specializations are not templates)
If you don't need endl between the arguments, you can do without specialization.
using WantedType = const char *;
template <class... Args>
std::enable_if_t<std::conjunction_v<std::is_same<WantedType, Args>...>>
show_log(Args... args)
{
(std::cout << ... << args) << std::endl;
}
Can anyone think of how to add linefeeds into this solution without specialization? Maybe it is not possible?
--- EDIT ---
As Christopher points out in comments, there is a neat single function solution:
using WantedType = const char *;
template <class... Args>
std::enable_if_t<std::conjunction_v<std::is_same<WantedType, Args>...>>
show_log(Args... args)
{
((std::cout << args << '\n'), ...) << std::flush;
}

Can I change the template argument deduction order for a generic variadic lambda?

Take the following code, which is a simplified example:
template <typename F>
void foo(F f) {
//bool some = is_variadic_v<F>; // Scenario #1
bool some = true; // Scenario #2
f(int(some), int(some));
}
int main() {
auto some = [](int i, int j) {
std::cout << i << " " << j << '\n';
};
foo([&some](auto... params) {
some(params...);
});
}
A function takes a generic variadic lambda and calls it with a fixed set of arguments. This lambda itself then just calls another function/lambda with a matching prototype.
As one could expect, in scenario 2, when f is called inside foo, the compiler will deduce params... to be the parameter pack {1, 1}.
For scenario #1, I am using a code from another Q&A to deduce the arity of a callable object. If however such an object is callable with more than a pre-defined maximum amount of arguments, it is considered "variadic". In detail, is_variadic_v will employ a form of expression SFINAE where it is attempted to call the function object with a decreasing number of arguments having an "arbitrary type" that is implictly convertible to anything.
The problem is now that apparently, the compiler will deduce F (and along its argument pack) during this metacode, and if it is variadic (such as in this case), it deduces F as a lambda taking the dummy arguments, i.e. something like main()::lambda(<arbitrary_type<0>, arbitrary_type<1>, arbitrary_type<2>, ..., arbitrary_type<N>>) if N is the "variadic limit" from above. Now params... is deduced as arbitrary_type<1>, arbitrary_type<2>, ... and correspondingly, the call some(params...) will fail.
This behaviour can be demonstrated in this little code example:
#include <utility>
#include <type_traits>
#include <iostream>
constexpr int max_arity = 12; // if a function takes more arguments than that, it will be considered variadic
struct variadic_type { };
// it is templated, to be able to create a
// "sequence" of arbitrary_t's of given size and
// hence, to 'simulate' an arbitrary function signature.
template <auto>
struct arbitrary_type {
// this type casts implicitly to anything,
// thus, it can represent an arbitrary type.
template <typename T>
operator T&&();
template <typename T>
operator T&();
};
template <
typename F, auto ...Ints,
typename = decltype(std::declval<F>()(arbitrary_type<Ints>{ }...))
>
constexpr auto test_signature(std::index_sequence<Ints...> s) {
return std::integral_constant<int, size(s)>{ };
}
template <auto I, typename F>
constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{ })) {
return { };
}
template <auto I, typename F, typename = std::enable_if_t<(I > 0)>>
constexpr auto arity_impl(...) {
// try the int overload which will only work,
// if F takes I-1 arguments. Otherwise this
// overload will be selected and we'll try it
// with one element less.
return arity_impl<I - 1, F>(0);
}
template <typename F, auto MaxArity>
constexpr auto arity_impl() {
// start checking function signatures with max_arity + 1 elements
constexpr auto tmp = arity_impl<MaxArity+1, F>(0);
if constexpr (tmp == MaxArity+1)
return variadic_type{ }; // if that works, F is considered variadic
else return tmp; // if not, tmp will be the correct arity of F
}
template <typename F, auto MaxArity = max_arity>
constexpr auto arity(F&&) { return arity_impl<std::decay_t<F>, MaxArity>(); }
template <typename F, auto MaxArity = max_arity>
constexpr auto arity_v = arity_impl<std::decay_t<F>, MaxArity>();
template <typename F, auto MaxArity = max_arity>
constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_type>;
template <typename F>
void foo(F f) {
bool some = is_variadic_v<F>;
//bool some = true;
f(int(some), int(some));
}
int main() {
auto some = [](int i, int j) {
std::cout << i << " " << j << '\n';
};
foo([&some](auto... params) {
some(params...);
});
}
Can I prevent this behaviour? Can I force the compiler to re-deduce the parameter list?
EDIT:
An additional peculiarity is that the compiler seems to act kind of schizophrenic. When I change the contents of foo to
foo([&some](auto... params) {
// int foo = std::index_sequence<sizeof...(params)>{ };
std::cout << sizeof...(params) << '\n';
});
the compiler will create a program that will print 2 in this example. If however I include the commented line (which, as it makes no sense, should trigger a compiler diagnostic), I get confronted with
error: cannot convert 'std::index_sequence<13>' {aka 'std::integer_sequence<long unsigned int, 13>'} to 'int' in initialization
85 | int foo = std::index_sequence<sizeof...(params)>{ };
so does the compiler now deduces sizeof...(params) to be 2 and 13 at the same time? Or did he change his mind and chooses now 13 just because I added another statement into the lambda? Compilation will also fail if I instead choose a static_assert(2 == sizeof...(params));. So the compiler deduces sizeof...(params) == 2, except if I ask him whether he did deduce 2, because then he didn't.
Apparently, it is very decisive for the parameter pack deduction what is written inside the lambda. Is it just me or does this behaviour really look pathologic?

Why is std::invocable concept blocking this code from compiling

I do not understand why neither the lambda nor the function are recognized as an std::invocable compliant types in the following code:
#include <concepts>
#include <iostream>
void f( std::invocable auto callback)
{
callback(47);
}
void function_callback(int i)
{
std::cout << i << std::endl;
}
auto lambda_callback = [](int i )
{
std::cout << i << std::endl;
};
int main(int)
{
f(&function_callback);
f(lambda_callback);
}
I am using GCC trunk with -std=c++2a flag enabled.
If you look at the definition of invocable (or in the standard):
template< class F, class... Args >
concept invocable =
requires(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
/* not required to be equality preserving */
};
What this means:
void f( std::invocable auto callback)
And it might be clearer if we write it long-form:
template <typename F>
requires std::invocable<F>
void f(F callback);
Is that F is invocable with no arguments - that it's a nullary function (Args... is an empty pack here). Neither your function nor your lambda are nullary functions - they're both unary, so the constraint correctly rejects those.
What you probably want is:
void f( std::invocable<int> auto callback)
which checks if callback is invocable with a single argument of type int.

How do I determine the number of parameters of a std::function?

I have the following problem. Say you want to write a generic function that can take a lambda expression. I understand that if the parameter is of type std::function, then I could not only use lambdas, but also functions and even pointers to functions. So at a first step, I did the following:
void print(std::function<void(int, int)> fn) {
fn(1,2);
}
int main() {
print([](int i, int j) { std::cout << j <<','<<i<<'\n'; });
return 0;
}
Now the problem is that I want to make this function generic, meaning that I don't want the lambda expression to have only two parameters.
So I tried changing the signature of the print function to something more generic like:
template <class function_type>
void print(function_type fn);
But now the problem is that the function takes ANY object and I'm not ok with that.
But the main problem is that, I have no idea how many parameters the object fn can accept.
So in a way I'm looking for a compile time way to determine how many arguments fn has, and if possible to change the type of fn to std::function. And then, given that I know the number of parameters that fn accepts, is there a generic way to pack an arbitrary number of parameters to be passed to fn? I don't even know if this is possible within C++11. What I mean is that given the number of arguments, is there a way to pack parameters to pass to fn? So that if there are two arguments, then I would call
fn(arg1, arg2);
if there are three:
fn(arg1, arg2, arg3);
and so on.
Thank you all for your insight.
aa
The following snippets might be useful.
This gives the number of arguments that a std::function takes
template <typename Signature>
struct count_args;
template <typename Ret, typename... Args>
struct count_args<std::function<Ret(Args...)>> {
static constexpr size_t value = sizeof...(Args);
};
For example the following code compiles (clang 3.2, gcc 4.7.2 and icc 13.1.0)
static_assert(count_args<std::function<void() >>::value == 0, "Ops!");
static_assert(count_args<std::function<void(int) >>::value == 1, "Ops!");
static_assert(count_args<std::function<void(int, int)>>::value == 2, "Ops!");
As far as I understand, you want to call the function object passing the correct number of arguments, right? Then for each argument we need to provide a value which is convertible to its type. A solution with this generality is very hard (or even impossible). Hence, I'll present two alternatives.
1 Each argument is a value initialized object of its type. (This is what ecatmur suggested.)
template <typename Ret, typename... Args>
Ret call(const std::function<Ret(Args...)>& f) {
return f(Args{}...); // for the intel compiler replace {} with ()
}
2 A fixed value is given and all the arguments are implicitly initialized from this value:
template <typename Ret, typename... Args, typename Val, typename... Vals>
typename std::enable_if<sizeof...(Args) == sizeof...(Vals), Ret>::type
call(const std::function<Ret(Args...)>& f, const Val&, const Vals&... vals) {
return f(vals...);
}
template <typename Ret, typename... Args, typename Val, typename... Vals>
typename std::enable_if<(sizeof...(Args) > sizeof...(Vals)), Ret>::type
call(const std::function<Ret(Args...)>& f, const Val& val, const Vals&... vals) {
return call(f, val, val, vals...);
}
The three overloads are unambiguous and can be used as the following examples show:
{
std::function<char()> f = []() -> char {
std::cout << "f() ";
return 'A';
};
std::cout << call(f) << std::endl; // calls f()
std::cout << call(f, 0) << std::endl; // calls f()
}
{
std::function<char(int)> f = [](int i) -> char {
std::cout << "f(" << i << ") ";
return 'B';
};
std::cout << call(f) << std::endl; // calls f(0)
std::cout << call(f, 1) << std::endl; // calls f(1)
}
{
std::function<char(int, int)> f = [](int i, int j) -> char {
std::cout << "f(" << i << "," << j << ") ";
return 'C';
};
std::cout << call(f) << std::endl; // calls f(0, 0)
std::cout << call(f, 2) << std::endl; // calls f(2, 2)
}
Yes you can pack as many parameters to fn as you wish using variadic templates.
template <class function_type, class... Args>
void print(function_type fn, Args... args)
{
//Call fn with args
fn(std::forward<Args>(args...));
}
To find out how many args there are in the parameter pack, you can use sizeof...(args).
To determine the signature of a callable, you can use the solution from Inferring the call signature of a lambda or arbitrary callable for "make_function". You can then package the callable into a std::function, or create a tag and use parameter inference:
template<typename T> struct tag {};
template<typename F, typename... Args>
void print_impl(F &&fn, tag<void(Args...)>) {
fn(Args{}...);
}
template<typename F>
void print(F &&fn) {
print_impl(std::forward<F>(fn), tag<get_signature<F>>{});
}
Note this uses value-initialised arguments; if you want anything more complex you can build a std::tuple<Args...> and pass that along, invoking it per "unpacking" a tuple to call a matching function pointer.

How can I iterate over a packed variadic template argument list?

I'm trying to find a method to iterate over an a pack variadic template argument list.
Now as with all iterations, you need some sort of method of knowing how many arguments are in the packed list, and more importantly how to individually get data from a packed argument list.
The general idea is to iterate over the list, store all data of type int into a vector, store all data of type char* into a vector, and store all data of type float, into a vector. During this process there also needs to be a seperate vector that stores individual chars of what order the arguments went in. As an example, when you push_back(a_float), you're also doing a push_back('f') which is simply storing an individual char to know the order of the data. I could also use a std::string here and simply use +=. The vector was just used as an example.
Now the way the thing is designed is the function itself is constructed using a macro, despite the evil intentions, it's required, as this is an experiment. So it's literally impossible to use a recursive call, since the actual implementation that will house all this will be expanded at compile time; and you cannot recruse a macro.
Despite all possible attempts, I'm still stuck at figuring out how to actually do this. So instead I'm using a more convoluted method that involves constructing a type, and passing that type into the varadic template, expanding it inside a vector and then simply iterating that. However I do not want to have to call the function like:
foo(arg(1), arg(2.0f), arg("three");
So the real question is how can I do without such? To give you guys a better understanding of what the code is actually doing, I've pasted the optimistic approach that I'm currently using.
struct any {
void do_i(int e) { INT = e; }
void do_f(float e) { FLOAT = e; }
void do_s(char* e) { STRING = e; }
int INT;
float FLOAT;
char *STRING;
};
template<typename T> struct get { T operator()(const any& t) { return T(); } };
template<> struct get<int> { int operator()(const any& t) { return t.INT; } };
template<> struct get<float> { float operator()(const any& t) { return t.FLOAT; } };
template<> struct get<char*> { char* operator()(const any& t) { return t.STRING; } };
#define def(name) \
template<typename... T> \
auto name (T... argv) -> any { \
std::initializer_list<any> argin = { argv... }; \
std::vector<any> args = argin;
#define get(name,T) get<T>()(args[name])
#define end }
any arg(int a) { any arg; arg.INT = a; return arg; }
any arg(float f) { any arg; arg.FLOAT = f; return arg; }
any arg(char* s) { any arg; arg.STRING = s; return arg; }
I know this is nasty, however it's a pure experiment, and will not be used in production code. It's purely an idea. It could probably be done a better way. But an example of how you would use this system:
def(foo)
int data = get(0, int);
std::cout << data << std::endl;
end
looks a lot like python. it works too, but the only problem is how you call this function.
Heres a quick example:
foo(arg(1000));
I'm required to construct a new any type, which is highly aesthetic, but thats not to say those macros are not either. Aside the point, I just want to the option of doing:
foo(1000);
I know it can be done, I just need some sort of iteration method, or more importantly some std::get method for packed variadic template argument lists. Which I'm sure can be done.
Also to note, I'm well aware that this is not exactly type friendly, as I'm only supporting int,float,char* and thats okay with me. I'm not requiring anything else, and I'll add checks to use type_traits to validate that the arguments passed are indeed the correct ones to produce a compile time error if data is incorrect. This is purely not an issue. I also don't need support for anything other then these POD types.
It would be highly apprecaited if I could get some constructive help, opposed to arguments about my purely illogical and stupid use of macros and POD only types. I'm well aware of how fragile and broken the code is. This is merley an experiment, and I can later rectify issues with non-POD data, and make it more type-safe and useable.
Thanks for your undertstanding, and I'm looking forward to help.
If your inputs are all of the same type, see OMGtechy's great answer.
For mixed-types we can use fold expressions (introduced in c++17) with a callable (in this case, a lambda):
#include <iostream>
template <class ... Ts>
void Foo (Ts && ... inputs)
{
int i = 0;
([&]
{
// Do things in your "loop" lambda
++i;
std::cout << "input " << i << " = " << inputs << std::endl;
} (), ...);
}
int main ()
{
Foo(2, 3, 4u, (int64_t) 9, 'a', 2.3);
}
Live demo
(Thanks to glades for pointing out in the comments that I didn't need to explicitly pass inputs to the lambda. This made it a lot neater.)
If you need return/breaks in your loop, here are some workarounds:
Demo using try/throw. Note that throws can cause tremendous slow down of this function; so only use this option if speed isn't important, or the break/returns are genuinely exceptional.
Demo using variable/if switches.
These latter answers are honestly a code smell, but shows it's general-purpose.
If you want to wrap arguments to any, you can use the following setup. I also made the any class a bit more usable, although it isn't technically an any class.
#include <vector>
#include <iostream>
struct any {
enum type {Int, Float, String};
any(int e) { m_data.INT = e; m_type = Int;}
any(float e) { m_data.FLOAT = e; m_type = Float;}
any(char* e) { m_data.STRING = e; m_type = String;}
type get_type() const { return m_type; }
int get_int() const { return m_data.INT; }
float get_float() const { return m_data.FLOAT; }
char* get_string() const { return m_data.STRING; }
private:
type m_type;
union {
int INT;
float FLOAT;
char *STRING;
} m_data;
};
template <class ...Args>
void foo_imp(const Args&... args)
{
std::vector<any> vec = {args...};
for (unsigned i = 0; i < vec.size(); ++i) {
switch (vec[i].get_type()) {
case any::Int: std::cout << vec[i].get_int() << '\n'; break;
case any::Float: std::cout << vec[i].get_float() << '\n'; break;
case any::String: std::cout << vec[i].get_string() << '\n'; break;
}
}
}
template <class ...Args>
void foo(Args... args)
{
foo_imp(any(args)...); //pass each arg to any constructor, and call foo_imp with resulting any objects
}
int main()
{
char s[] = "Hello";
foo(1, 3.4f, s);
}
It is however possible to write functions to access the nth argument in a variadic template function and to apply a function to each argument, which might be a better way of doing whatever you want to achieve.
Range based for loops are wonderful:
#include <iostream>
#include <any>
template <typename... Things>
void printVariadic(Things... things) {
for(const auto p : {things...}) {
std::cout << p.type().name() << std::endl;
}
}
int main() {
printVariadic(std::any(42), std::any('?'), std::any("C++"));
}
For me, this produces the output:
i
c
PKc
Here's an example without std::any, which might be easier to understand for those not familiar with std::type_info:
#include <iostream>
template <typename... Things>
void printVariadic(Things... things) {
for(const auto p : {things...}) {
std::cout << p << std::endl;
}
}
int main() {
printVariadic(1, 2, 3);
}
As you might expect, this produces:
1
2
3
You can create a container of it by initializing it with your parameter pack between {}. As long as the type of params... is homogeneous or at least convertable to the element type of your container, it will work. (tested with g++ 4.6.1)
#include <array>
template <class... Params>
void f(Params... params) {
std::array<int, sizeof...(params)> list = {params...};
}
This is not how one would typically use Variadic templates, not at all.
Iterations over a variadic pack is not possible, as per the language rules, so you need to turn toward recursion.
class Stock
{
public:
bool isInt(size_t i) { return _indexes.at(i).first == Int; }
int getInt(size_t i) { assert(isInt(i)); return _ints.at(_indexes.at(i).second); }
// push (a)
template <typename... Args>
void push(int i, Args... args) {
_indexes.push_back(std::make_pair(Int, _ints.size()));
_ints.push_back(i);
this->push(args...);
}
// push (b)
template <typename... Args>
void push(float f, Args... args) {
_indexes.push_back(std::make_pair(Float, _floats.size()));
_floats.push_back(f);
this->push(args...);
}
private:
// push (c)
void push() {}
enum Type { Int, Float; };
typedef size_t Index;
std::vector<std::pair<Type,Index>> _indexes;
std::vector<int> _ints;
std::vector<float> _floats;
};
Example (in action), suppose we have Stock stock;:
stock.push(1, 3.2f, 4, 5, 4.2f); is resolved to (a) as the first argument is an int
this->push(args...) is expanded to this->push(3.2f, 4, 5, 4.2f);, which is resolved to (b) as the first argument is a float
this->push(args...) is expanded to this->push(4, 5, 4.2f);, which is resolved to (a) as the first argument is an int
this->push(args...) is expanded to this->push(5, 4.2f);, which is resolved to (a) as the first argument is an int
this->push(args...) is expanded to this->push(4.2f);, which is resolved to (b) as the first argument is a float
this->push(args...) is expanded to this->push();, which is resolved to (c) as there is no argument, thus ending the recursion
Thus:
Adding another type to handle is as simple as adding another overload, changing the first type (for example, std::string const&)
If a completely different type is passed (say Foo), then no overload can be selected, resulting in a compile-time error.
One caveat: Automatic conversion means a double would select overload (b) and a short would select overload (a). If this is not desired, then SFINAE need be introduced which makes the method slightly more complicated (well, their signatures at least), example:
template <typename T, typename... Args>
typename std::enable_if<is_int<T>::value>::type push(T i, Args... args);
Where is_int would be something like:
template <typename T> struct is_int { static bool constexpr value = false; };
template <> struct is_int<int> { static bool constexpr value = true; };
Another alternative, though, would be to consider a variant type. For example:
typedef boost::variant<int, float, std::string> Variant;
It exists already, with all utilities, it can be stored in a vector, copied, etc... and seems really much like what you need, even though it does not use Variadic Templates.
There is no specific feature for it right now but there are some workarounds you can use.
Using initialization list
One workaround uses the fact, that subexpressions of initialization lists are evaluated in order. int a[] = {get1(), get2()} will execute get1 before executing get2. Maybe fold expressions will come handy for similar techniques in the future. To call do() on every argument, you can do something like this:
template <class... Args>
void doSomething(Args... args) {
int x[] = {args.do()...};
}
However, this will only work when do() is returning an int. You can use the comma operator to support operations which do not return a proper value.
template <class... Args>
void doSomething(Args... args) {
int x[] = {(args.do(), 0)...};
}
To do more complex things, you can put them in another function:
template <class Arg>
void process(Arg arg, int &someOtherData) {
// You can do something with arg here.
}
template <class... Args>
void doSomething(Args... args) {
int someOtherData;
int x[] = {(process(args, someOtherData), 0)...};
}
Note that with generic lambdas (C++14), you can define a function to do this boilerplate for you.
template <class F, class... Args>
void do_for(F f, Args... args) {
int x[] = {(f(args), 0)...};
}
template <class... Args>
void doSomething(Args... args) {
do_for([&](auto arg) {
// You can do something with arg here.
}, args...);
}
Using recursion
Another possibility is to use recursion. Here is a small example that defines a similar function do_for as above.
template <class F, class First, class... Rest>
void do_for(F f, First first, Rest... rest) {
f(first);
do_for(f, rest...);
}
template <class F>
void do_for(F f) {
// Parameter pack is empty.
}
template <class... Args>
void doSomething(Args... args) {
do_for([&](auto arg) {
// You can do something with arg here.
}, args...);
}
You can't iterate, but you can recurse over the list. Check the printf() example on wikipedia: http://en.wikipedia.org/wiki/C++0x#Variadic_templates
You can use multiple variadic templates, this is a bit messy, but it works and is easy to understand.
You simply have a function with the variadic template like so:
template <typename ...ArgsType >
void function(ArgsType... Args){
helperFunction(Args...);
}
And a helper function like so:
void helperFunction() {}
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {
//do what you want with t
function(Args...);
}
Now when you call "function" the "helperFunction" will be called and isolate the first passed parameter from the rest, this variable can b used to call another function (or something). Then "function" will be called again and again until there are no more variables left. Note you might have to declare helperClass before "function".
The final code will look like this:
void helperFunction();
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args);
template <typename ...ArgsType >
void function(ArgsType... Args){
helperFunction(Args...);
}
void helperFunction() {}
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {
//do what you want with t
function(Args...);
}
The code is not tested.
#include <iostream>
template <typename Fun>
void iteratePack(const Fun&) {}
template <typename Fun, typename Arg, typename ... Args>
void iteratePack(const Fun &fun, Arg &&arg, Args&& ... args)
{
fun(std::forward<Arg>(arg));
iteratePack(fun, std::forward<Args>(args)...);
}
template <typename ... Args>
void test(const Args& ... args)
{
iteratePack([&](auto &arg)
{
std::cout << arg << std::endl;
},
args...);
}
int main()
{
test(20, "hello", 40);
return 0;
}
Output:
20
hello
40