0

The function f() below takes the callable argument fc whose signature is nicely visible in the function definition.

// scaffolding for function definition
template<typename T>
struct Identity {   
    using type = T;
};

// still scaffolding for function definition  
template<typename T>
using Make_deducible = typename Identity<T>::type;  

// function definiton
template <typename T>
T f(T val, Make_deducible<std::function<T(T)>> fc) // we see fc's expected signature
{ return fc(val); }

Note that it can be called in all the following ways:

int g(int i) { return i * i; };
f(5, g); // call with function ptr: rarely used

std::function<int(int)> fo{[](int i){ return i * i; }};
f(6, fo); // call with std::function object: tedious

f(7, [](int i){ return i * i; }); // call with lambda closure: usual use case

What bothers me is that the definition of f() requires some scaffolding to work. So, my questions is:

Is there a less roundabout way of doing this while keeping the signature of get_val visible in definition, and still have the function be callable with a lambda?

Kemal
  • 849
  • 5
  • 21
  • 2
    Make deducible blocks deduction. That seems like a poor choice of names. Or a serious misunderstanding. Maybe both. – Yakk - Adam Nevraumont Jul 28 '17 at 13:28
  • 2
    Couldn't you just use `std::common_type_t` ? – MSalters Jul 28 '17 at 13:31
  • What exactly are you trying to do? You function can be as simple as `template T f(T val, Func f) { reutrn f(val); }`. – NathanOliver Jul 28 '17 at 13:37
  • @NathanOliver: I don't see the expected signature of `f` in that way. – Kemal Jul 28 '17 at 13:39
  • @Yakk : You are probably right. Obviously I am not well versed in templates. Naming it that way came about after the fact that, without it, I get the compiler error saying something like "could not deduce template argument from lambda". I just made the pain go away. – Kemal Jul 28 '17 at 13:46

2 Answers2

1

You can check whether the callable is convertible to std::function<T(T)>:

template<class T, class F>
std::enable_if_t<std::is_convertible<F, std::function<T(T)>>::value, T>
f(T v, F fn) {
    return fn(v);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
-1

Make deducible blocks the compiler from trying tomdeduce that argument. Which seems like a poor choice of name; that is usually called block_deduction or something.

The easiest way to include the signature is a comment:

template <class T, class F /*T(T) signature*/>
T f(T val, F fc)
{ return fc(val); }

wherever you want it.

If we want compiler enforcing, we can block deduction and type erase like you did in your question. Another choice:

template<class Sig, class F, class=void>
struct can_call:std::false_type{};
template<class R, class...Args, class F>
struct can_call<R(Args...),F,std::enable_if_t<
  std::is_convertible<std::result_of_t<F(Args...)>,R>{}
>>:std::true_type{};
template<class...Args, class F>
struct can_call<void(Args...),F,std::void_t<
  std::result_of_t<F(Args...)>,R>{}
>>:std::true_type{};

that defines can_call<Sig, F> which is true if and only if you can call F in a context compatible with Sig.

template <class T, class F /*T(T) signature*/>
std::enable_if_t<can_call<T(T),F&>{}, T> T f(T val, F fc)
{ return fc(val); }

and now F is checked at overload resolution time to ensure it is compatible.

I used some C++14 and C++17 std::void_t. void_t is easy to write in C++11, and easy to find on SO.

There may be typos; on phone.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524