Abhinav Gauniyal
Abhinav Gauniyal

Reputation: 7574

Convert overloaded functions to specialized function templates

I've a function that is currently overloaded for different data types and takes a lambda(function pointer) to initialize those data types. I'm in process of converting them to template instances but haven't been successful yet.

Here's the overloaded version -

#include <iostream>
using namespace std;


void doSome(int (*func)(int &)){
    int a;
    a = 5;
    int res = func(a);
    cout << a << "\n";
}


void doSome(int (*func)(double &)){
    double a;
    a = 5.2;
    int res = func(a);
    cout << a << "\n";
}


int main() {
    doSome([](int &a){
        a += 2;
        return 1;
    });

    doSome([](double &a){
        a += 2.5;
        return 1;
    });
    return 0;
}

Note that I've taken example of int and double for simplification, they might be some entirely different(and complex) types in actual code.


Here's what I've tried yet -

#include <iostream>
using namespace std;

template <typename F, typename S>
void doSome(F &func){
    S a;
    auto res = func(a);
    cout << res << "\n";
}

template<>
void doSome<typename F, int> (F &func){
    int a;
    a = 5;
    auto res = func(a);
    cout << res << "\n";
}

template<>
void dpSome<typename F, double> (F &func){
    double a;
    a = 5.5
    auto res = func(a);
    cout << res << "\n";
}


int main() {
    doSome([](int &a){
        a += 2;
        return 1;
    });

    doSome([](double &a){
        a += 2.5;
        return 1;
    });
    return 0;
}

Also while invoking templated functions, if I don't have to pass <any type hints> to the function, that would be much better solution.

Upvotes: 2

Views: 160

Answers (2)

If you want to make a generic version of doSome(), which doesn't use SFINAE for overload resolution, it gets a bit more complex.

#include <type_traits> // For std::remove_reference_t.

namespace detail {
    // Helper to isolate return and parameter types, for a single-parameter callable.
    template<typename T>
    struct isolate_types;

    // Function.
    template<typename R, typename P>
    struct isolate_types<R(P)>              { using Ret = R; using Param = P; };

    // Function pointer.
    template<typename R, typename P>
    struct isolate_types<R(*)(P)>           { using Ret = R; using Param = P; }

    // Pointer-to-member-function.  Used for lambdas & functors.
    // Assumes const this pointer.
    template<typename R, typename C, typename P>
    struct isolate_types<R (C::*)(P) const> { using Ret = R; using Param = P; };

    // Lambda.  Uses lambda's operator().
    // Credit goes to ecatmur: http://stackoverflow.com/a/13359520/5386374
    template<typename T>
    struct isolate_types : isolate_types<decltype(&std::remove_reference_t<T>::operator())> {};

    // Individual type aliases.
    template<typename T>
    using IsolateReturn = typename isolate_types<T>::Ret;
    template<typename T>
    using IsolateParam  = typename isolate_types<T>::Param;

    // Internal values, used by doSome().
    template<typename T> T value;

    template<> constexpr    int value<int>    = 5;
    template<> constexpr double value<double> = 5.2;
    // Define others as needed...
} // namespace detail

template<typename F>
void doSome(F func) {
    // Determine necessary types.
    using Ret   = detail::IsolateReturn<F>;
    using Param = std::remove_reference_t<detail::IsolateParam<F>>;

    // And voila.
    Param a = detail::value<Param>;
    Ret res = func(a); // Can also use auto, if Ret isn't needed elsewhere.
    std::cout << a << "\n";
}

Plugging this into your code... and it works.


Note that I'm not sure if this will work with all lambdas as written, and that it currently won't work with references to functions. It's easy enough to extend, however, by adding additional specialisations of isolate_types.

Upvotes: 1

Barry
Barry

Reputation: 302767

There are a few issues with your approach. First, you can't partially specialize function templates, so that's out from the gate. Second, you're taking your function by lvalue reference - which prevents you from passing in a lambda, which is a prvalue.


In this case, it's easy to just add some SFINAE on your function template so that one only participates in overload resolution if it can be called with int& and the other only with double&:

template <class F>
auto doSome(F f)
    -> decltype(f(std::declval<int&>()), void())
{
    // int& case
}        

template <class F>
auto doSome(F f)
    -> decltype(f(std::declval<double&>()), void())
{
    // double& case
}        

Upvotes: 5

Related Questions