Deducing type for overloaded functions - currying

888 views Asked by At

Given a callable object ( a function ) a, and an argument b ( or a series of arguments ), I would like to deduce the type returned from f considering that f is overloaded with multiple signatures.

one of my many attempts is

#include <iostream>
#include <cstdint>
#include <string>
#include <functional>
#include <utility>
#include <typeinfo>

int foo(uint32_t a) { return ((a + 0) * 2); }

bool foo(std::string a) { return (a.empty()); }

/*template <typename A, typename B> auto bar(A a, B b) -> decltype(a(b)) {
  return (a(b));
}*/

/*template <typename A, typename B> decltype(std::declval<a(b)>()) bar(A a, B b)
{
  return (a(b));
}*/

template <typename A, typename B> void bar(std::function<A(B)> a, B b) {
  std::cout << a(b) << "\n";
}

int main() {

  // the following 2 lines are trivial and they are working as expected
  std::cout << foo(33) << "\n";
  std::cout << typeid(decltype(foo(std::string("nothing")))).name() << "\n";

  std::cout << bar(foo, 33) << "\n";
  //std::cout << bar(foo, std::string("Heinz")) << "\n";

  return (0);
}

and 2 templates options are commented out and included in the previous code.

I'm using declval result_of auto decltype without any luck.

How does the overloading resolution process works at compile time ?

If anyone wants to know why I'm trying to get creative with this, is that I'm trying to implement some Currying in C++11 in a workable/neat way.

3

There are 3 answers

5
Dietmar Kühl On BEST ANSWER

The problem is that you can't easily create a function object from an overload set: when you state foo or &foo (the function decays into a function pointer in most case, I think) you don't get an object but you get an overload set. You can tell the compiler which overload you want by either calling it or providing its signature. As far as I can tell, you don't want either.

The only approach I'm aware of is to turn your function into an actual function object which makes the problem go away:

struct foo_object
{
    template <typename... Args>
    auto operator()(Args&&... args) -> decltype(foo(std::forward<Args>(args)...)) {
        return foo(std::forward<Args>(args)...);
    }
};

With that wrapper which is unfortunately needed for each name, you can trivially deduce the return type, e.g.:

template <typename Func, typename... Args>
auto bar(Func func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) {
    // do something interesting
    return func(std::forward<Args>(args)...);
}

int main() {
    bar(foo_object(), 17);
    bar(foo_object(), "hello");
}

It doesn't quite solve the problem of dealing with overload sets but it gets reasonably close. I experimented with this idea, essentially also for the purpose of currying in the context of an improved system of standard library algorithms and I'm leaning towards the algorithms actually being function objects rather than functions (this is desirable for various other reasons, too; e.g., you don't need to faff about when you want to customize on algorithm with another one).

10
aaronman On

This is actually already implemented for you std::result_of. Here is a possible implementation

template<class>
struct result_of;

// C++11 implementation, does not satisfy C++14 requirements
template<class F, class... ArgTypes>
struct result_of<F(ArgTypes...)>
{
    typedef decltype(
                     std::declval<F>()(std::declval<ArgTypes>()...)
                    ) type;
};
1
ScarletAmaranth On

If foo is overloaded, you need to use the following:

#include <type_traits>

int foo(int);
float foo(float);

int main() {
    static_assert(std::is_same<decltype(foo(std::declval<int>())), int>::value, "Nope.");
    static_assert(std::is_same<decltype(foo(std::declval<float>())), float>::value, "Nope2.");
}

If it's not, then this will suffice:

#include <type_traits>
bool bar(int);

int main() {
    static_assert(std::is_same<std::result_of<decltype(bar)&(int)>::type, bool>::value, "Nope3.");
}

Yes, it is verbose because you're trying to explicitly extract what implicit ad-hoc overloading does for you.