Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded function as argument of variadic template function

I'm trying to make variadic template function, which takes as arguments overloaded function and its arguments :)

int sumall(int a) { return a; }
int sumall(int a, int b) { return a+b; }

template<typename R, typename... A>
R doit( R(*f)(A...), A... a) {
    return f(a...); }

I want to call doit without any template specifiers nor casting:

cout << doit(sumall, 7, 6) << endl

That doesn't compile, but when return types are void, everything work perfect:

void printsum(int a) { cout << a << endl; }
void printsum(int a, int b) { cout << a+b << endl; }

template<typename... A>
void vdoit( void(*f)(A...), A... a) {
    f(a...); }

// ...
vdoit(printsum, 7, 6);

Is it possible to modify first template to work with modyfing only doit template (I want to preserve sumall functions and doit call)? I think it can be done with removing typename R and leaving just template<typename... A> since R depends on A... and f, but I don't have any idea how to show that dependency.

like image 807
krdln Avatar asked Jan 29 '12 16:01

krdln


People also ask

Can template function be overloaded?

You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration.

What is the use of Variadic templates?

With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.


2 Answers

When taking a pointer of a function the compiler needs to know which of the overloads you want to use. There is no way to pass a pointer to an "overload set" and have the compiler decide later. Neither of you examples works with any of the compilers I tried (very recent versions of EDG, gcc, and clang).

I don't think you can do what you want without changing the notation of your call. If you are willing to change the call you can encapsulate the knowledge about the function to be called into a class, e.g.:

struct sumall_t {
    template <typename... T>
    auto operator()(T... args) -> decltype(sumall(args...)) {
        return sumall(args...);
    }
};

This effectively creates a wrapper for an overload set. Since the result type can't be deduced directly and may depend on how the function is called, you'd need to use a different version of doit() as well:

template<typename Func, typename... A>
auto doit( Func f, A... a) ->decltype(f(a...)) {
    return f(a...);
}

This would then be used something like this:

doit(sumall_t(), 1, 2);

Another way to fix this is to mandate specification of the result type: in some way you try to do two things at once: you want to deduce the result type of the function to be called and you want to guide the compiler to choose a specific overload of a result set. However, these are interdependent. If you remove any dependency on deducing any template from the function pointer, you don't need wrap the overload set because you can determine the choice of overloaded function from the first argument to the function. In case you claim that "my compiler can do it if the return type isn't void" I'd say that your compiler is actually wrong in doing this.

like image 179
Dietmar Kühl Avatar answered Sep 21 '22 03:09

Dietmar Kühl


(If you're prepared to use variadic macros, then scroll to the end of this answer to see a better answer which make everything fully variadic. But I think that variadic macros are just a g++ extension.)

It can be made to work, if you're prepared to put the name of the function at the end of the parameter list. By putting it later, the compiler can deduce the necessary types from the earlier parameters to doit:

cout << doit(7, 6, sumall) << endl;
cout << doit(10, sumall) << endl;

Here is a demo on ideone.

The downside is that you have to implement one doit for each number of parameters. I've only implemented it for one- and two- parameter functions, but it shouldn't be a problem to extend this:

int sumall(int a) { return a; }
int sumall(int a, int b) { return a+b; }

template<typename A1, typename A2, typename R>
auto doit( A1 a1, A2 a2, R (*f) (A1,A2)) -> R {
    return f(a1, a2);
}
template<typename A1, typename R>
auto doit( A1 a1, R (*f) (A1)) -> R {
    return f(a1);
}

Update: Sometimes, it might appear that you can get away with having f as the first argument. But that's not as robust as putting it at the end. Consider the example where where are two functions that take the same number of arguments, but different types of parameters. e.g.:

int sumall(int a, int b) { return a+b; }
string sumall(string a, string b) { return a+" "+b; }

You need to have the function as the last argument, in order that the template deduction can use the type and number of parameters at the start to deduce the types of the arguments. Here's a demo on ideone of function-arg first and function-arg last.

The only downside with putting the arg at the end is that we can't then use variadic templates - variadic arg packs must be at the end. And you must get the types exactly right - see how I had to use string("hi") instead of simply "hi".

Using variadic macros to have the best of all worlds

By implementing doit as a macro, and using variadic macros (a gcc/g++ extension), it is possible to have a fully variadic solution with the function name appearing first. A demo on ideone.

cout << doit(sumall, 7, 6) << endl;
cout << doit(sumall, 10) << endl;
cout << doit(sumall, string("hi"), string("world")) << endl;

By using decltype and a couple of other simple classes, we can use the args provided to deduce the types of the args and then it can use that to select the right method from the overload set and deduce the return type from that.

template<typename ...Args>
struct OverloadResolved {
        template<typename R>
        static auto static_doit( R (*f) (Args...), Args ... args ) -> R {
                return f(args...);
        }
};

template<typename ...Args>
auto deduce(Args...) -> OverloadResolved<Args...> {
        return OverloadResolved<Args...>();
}

template<typename T>
struct dummy : public T { };

#define doit(f, ...) ( dummy<decltype(deduce( __VA_ARGS__ ))> :: static_doit(f, __VA_ARGS__) )

I'm pretty sure this is a safe use of macros, nothing will be evaluated twice (nothing inside decltype actually executes.

like image 38
Aaron McDaid Avatar answered Sep 20 '22 03:09

Aaron McDaid