2

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.

Abhinav Gauniyal
  • 7,034
  • 7
  • 50
  • 93
  • 1
    `template<> void doSome (F &func)` is wrong, and even if you have written `template void doSome (F &func)`, you can't partially specialize function templates – xinaiz Feb 20 '17 at 16:04
  • I'm just curious, why do you want to convert from overloading, to template specialization? Overloading is typically nicer and less surprising than function template specializations. Overloading is the reason why you don't need partial specialization for function templates. My advice to someone would be to avoid specializing function templates wherever possible. Is it possible that this is an XY problem? – Nir Friedman Feb 20 '17 at 16:10
  • 1
    @NirFriedman function pointers only work for non-capturing lambdas, and that's very limiting. `std::function` would've solved this and I've used it everywhere else but these functions are performance critical and benchmarks stated function pointers to be much faster in our case, so.. – Abhinav Gauniyal Feb 20 '17 at 16:14

2 Answers2

5

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
}        
Barry
  • 286,269
  • 29
  • 621
  • 977
  • can you explain a bit about `-> decltype(f(std::declval()), void())`? How is it working here? Doesn't `->` means return value expression ahead, and both methods have int as return value and what's that `void()` doing here! – Abhinav Gauniyal Feb 20 '17 at 16:11
  • @AbhinavGauniyal [Expression SFINAE](http://stackoverflow.com/q/12654067/2069064) – Barry Feb 20 '17 at 16:14
  • I read that answer and understood the gist of it. Still I couldn't grasp what's that `void()` doing there since [decltype](http://en.cppreference.com/w/cpp/language/decltype) doesn't takes two params, or is it the syntax of implementing Expression SFINAE? – Abhinav Gauniyal Feb 20 '17 at 16:17
  • 2
    @AbhinavGauniyal It's not passing two parameters. It's one expression, that's two separate expressions separated by a comma. Recall that comma throws out the first expression and yields the result of the second. – Barry Feb 20 '17 at 16:24
  • `decltype(void(f(std::declval())))` is simpler, fewer characters, and doesn't rely on `,`. – Yakk - Adam Nevraumont Feb 21 '17 at 15:22
1

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.