3

I need to extract the type of a function object parameter.

Lambdas get translated into a closure object with the operator(). std::function has got the operator(), too.

So, I can get a pointer to the operator() to pass to another function, in this way:

template <typename F, typename T, typename R, typename ... Args>
void helper(F&, R (T::*)(Args...) const)
{
    // do something with Args types
}

template <typename F>
void bar(F f)
{
    helper(f, &F::operator());
}

void freefunc(int) {}

void foo()
{
    // lambda: ok
    bar([](int){});
    // std::function: ok
    const std::function<void(double)> f = [](double){};
    bar(f);
    // std::bind: does not compile
    auto g = std::bind(freefunc, std::placeholders::_1);
    bar(g);
}

std::bind should create an object with the operator(), too. However, my code does not work with std::bind(), and I cannot understand why.

gcc produces this error:

In instantiation of 'void bar(F) [with F = std::_Bind<void (*(std::_Placeholder<1>))(int)>]':
<source>:58:8:   required from here
<source>:47:11: error: no matching function for call to 'helper(std::_Bind<void (*(std::_Placeholder<1>))(int)>&, <unresolved overloaded function type>)'
   47 |     helper(f, &F::operator());
      |     ~~~~~~^~~~~~~~~~~~~~~~~~~
<source>:39:6: note: candidate: 'template<class F, class T, class R, class ... Args> void helper(F&, R (T::*)(Args ...) const)'
   39 | void helper(F&, R (T::*)(Args...) const)
      |      ^~~~~~
<source>:39:6: note:   template argument deduction/substitution failed:
<source>:47:11: note:   couldn't deduce template parameter 'T'
   47 |     helper(f, &F::operator());
      |     ~~~~~~^~~~~~~~~~~~~~~~~~~
ASM generation compiler returned: 1
<source>: In instantiation of 'void bar(F) [with F = std::_Bind<void (*(std::_Placeholder<1>))(int)>]':
<source>:58:8:   required from here
<source>:47:11: error: no matching function for call to 'helper(std::_Bind<void (*(std::_Placeholder<1>))(int)>&, <unresolved overloaded function type>)'
   47 |     helper(f, &F::operator());
      |     ~~~~~~^~~~~~~~~~~~~~~~~~~
<source>:39:6: note: candidate: 'template<class F, class T, class R, class ... Args> void helper(F&, R (T::*)(Args ...) const)'
   39 | void helper(F&, R (T::*)(Args...) const)
      |      ^~~~~~
<source>:39:6: note:   template argument deduction/substitution failed:
<source>:47:11: note:   couldn't deduce template parameter 'T'
   47 |     helper(f, &F::operator());

What's the correct way to do the same with std::bind?

Daniele Pallastrelli
  • 2,430
  • 1
  • 21
  • 37
  • BTW: probably if you need to extract function parameter types you are doing something wrong. In most cases you just need to check if you can call given function with given set of parameters. You don't need to extract anything to do that – bartop Jan 18 '22 at 10:26
  • I *do* need the type Args inside helper, because in my real code, helper() must instantiate a template class parametrized over Args. For good reasons :-) If you want the real code is here: https://github.com/daniele77/cli/blob/63eb02970fc19c2ba182be4c30d5dcd79e63f438/include/cli/cli.h#L410 – Daniele Pallastrelli Jan 18 '22 at 10:34

2 Answers2

2

What you want to do is unfortunately impossible because the return type of std::bind is too loosely specified by the standard.

  • std::function::operator() is clearly defined by the standard, so you can match it against R (T::*)(Args... ), see [func.wrap.func.general],
  • for lambda functions, it's not that clear from [expr.prim.lambda.closure#3], but I'd say that it should work,
  • for std::bind, the specification [func.bind.bind#4] is much broader because it only says that you can call g(u1, u2, …, uM) where g is the returned value from std::bind, so there is no guarantee that the return type of std::bind even has an operator() member-function.

The actual implementation problem here, which is the same for gcc, clang and msvc, is that the operator() member-function of the return value is actually a template, so you cannot use &F::operator() directly — you cannot take the address of a templated (member-)function.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • So: let say I cannot get the address of operator() because it's a template. Is there a way to get the types of arguments of the bind operator () ? Inside helper() function I need to instantiate a template class passing the template arguments of the function generated by bind. – Daniele Pallastrelli Jan 18 '22 at 10:38
  • 1
    @DanielePallastrelli Not that I know of. The only way to deduce the type `operator()` if it's a templated member-function would be to actually know the type of the arguments... Actually, the problem is a bit more complex because for a single returned object, you might have multiple valid overload. In particular, it's not unusual for implementation to have overload that take any number of arguments - In your example, you can call `g(3.0, "foo", 23)` and it will compile (the behavior might be UB though). – Holt Jan 18 '22 at 10:44
2

In short - you cannot do this for std::bind or at least it cannot be guaranteed. Even though result of std::bind is closure-like it is quite different. Consider what would happen if something along the lines of class below was result of std::bind:

class bind_result {

template<class T, class U>
U operator()(const T&);

private:
// implementation details
};

Indeed, your function g would not work for class like that, since the operator() is a template.

bartop
  • 9,971
  • 1
  • 23
  • 54