3
#include <functional>
template <typename M>
M g(std::function<M(int)> f) {
    return f(0);
}

int main() {
    g([](int x){return x + 1;});
    return 0;
}

I want to express something like "the (only) argument passed to function g should be a callable object that has int as type of the parameter".

G++ 9.3.0 says

prog.cc: In function 'int main()':
prog.cc:8:31: error: no matching function for call to 'g(main()::<lambda(int)>)'
    8 |     g([](int x){return x + 1;});
      |                               ^
prog.cc:3:3: note: candidate: 'template<class M> M g(std::function<M(int)>)'
    3 | M g(std::function<M(int)> f) {
      |   ^
prog.cc:3:3: note:   template argument deduction/substitution failed:
prog.cc:8:31: note:   'main()::<lambda(int)>' is not derived from 'std::function<M(int)>'
    8 |     g([](int x){return x + 1;});
      |                               ^

What is wrong with the above attempt? And how should I achieve that intention?

You may want to see the code snippet on Wandbox here.

Jiashu Zou
  • 148
  • 5
  • 3
    Why do you feel you need to enforce it like this? – Asteroids With Wings May 10 '20 at 17:00
  • Do you want the function to be passed in to have `int` as the type of the parameter, or do you want to accept any function that can be called with an `int`? – cigien May 10 '20 at 17:25
  • Why do you want to restrict it this way? What exactly does it buy you compared to `template auto f(M m) { return m(0); }`? http://coliru.stacked-crooked.com/a/3a9e94caf357f0b9 – n. m. could be an AI May 10 '20 at 17:27
  • @n.'pronouns'm. I want to enforce the parameter type of `m` is exactly `int` and forbid such call as `f([](double x) { // .... });`. – Jiashu Zou May 10 '20 at 17:53
  • @cigien I want the function to be passed in to have `int` as the type of the parameter. – Jiashu Zou May 10 '20 at 17:56
  • This is rather impossible. See this http://coliru.stacked-crooked.com/a/b1044d64f7c227d5 – n. m. could be an AI May 10 '20 at 18:07
  • @n.'pronouns'm. Then I would like the compiler to at least warn on conversion from `std::function` to `std::function`. And is there any way to enforce the parameter type of the callable object passed to `f` at all? – Jiashu Zou May 10 '20 at 18:20
  • There is a constructor in std::function that accepts *any* callable object. It is implemented with regular C++ code, no magic. You can try to implement one yourself and see why it would be impissible (or at least magical) to provide a warning. – n. m. could be an AI May 10 '20 at 18:37
  • The duplicate target answers "why this doesn't compile?" but not "how to achieve the intention of checking the parameter". Voted to reopen. – cigien May 10 '20 at 19:26
  • @cigien I have changed the title to emphasize that. – Jiashu Zou May 10 '20 at 19:48

3 Answers3

1

Counterintuitively, you're not actually supposed to use std::function to take a function parameter. Instead, you just need a plain template parameter.

Using a plain template parameter works with std::function. But it also works with function pointers, and will let the function use a lambda (or any other class with an operator()) directly, without the performance hit of std::function.

template <typename F>
auto g(F f) -> decltype(f(std::declval<int>())) { //alternatively, `decltype(f(0))`
    return f(0);
}

If f does not have a member function which can take the expression 0 as input, then compilation will fail, so the constraint is enforced. This is actually the case regardless of whether you include the -> decltype part.


Actually answering the question in the title is a bit trickier. There are two things at play:

The first is that a lambda expression is not a std::function instantiation, even if it can be wrapped inside of one. A given concrete std::function instantiation, such as std::function<int(int)>, has a constructor which will convert from a lambda, and that's how we typically use std::function.

However, implicit conversions do not play well with templates. Either a function argument is used to deduce template parameters, in which case that function argument must match, or the function argument is totally concrete, in which case implicit conversions can be performed. But not both, which is what you had tried to do.

hegel5000
  • 876
  • 1
  • 7
  • 13
1

First, write a simple meta-function that gives the type of the argument of a unary function pointer:

template<class Ret, class Arg>
auto arg(Ret(*)(Arg)) -> Arg;

Then, in the body of g, you can static_assert that the passed in callable (when decayed to a function pointer), has a single parameter of type int:

template <typename Function>
auto g(Function f) { 
    static_assert(std::is_same_v<decltype(arg(+f)), int>);
    return f(0);
}

Now calls to g will only compile if this constraint is satisfied:

int a(int x) { return x + 1; }
int b(double x) { return x + 1; }

int main() {
    g([](int x){return x + 1;});      // ok
    g([](char x){return x + 1;});     // error
    g([](int x, int){return x + 1;}); // error
    g(a);                             // ok
    g(b);                             // error
}

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • Not every callable decays to a function pointer, – n. m. could be an AI May 11 '20 at 15:19
  • @n.'pronouns'm. Yes, this won't work for member functions I think. Are there any other cases for which this doesn't work? I'll try to fix it, if I can. – cigien May 11 '20 at 15:38
  • You can overload for member functions, they are simple. Capturing lambdas and other function objects (including `std::function`). are more problematic. You would need to extract their `operator()` and examine its type, and there might be overloaded or templated `operator()`. – n. m. could be an AI May 11 '20 at 15:48
0

The below code worked for me

#include <iostream>
#include <functional>

template <typename M>
M g(std::function<M (int)> f) {
  return f(0);
}

int main() {
  std::cout << g<int>([](int x){return x + 1; });
  return 0;
}
Nitheesh George
  • 1,357
  • 10
  • 13