Reputation: 23
I want to create a method that takes some parameter T, boolean function and does something with it (let's say print it if func(param) is true). The problem I have encountered is that:
when you write code without templates, both following examples work:
static bool is_even(int i) {
return i % 2 == 0;
}
void f(const int &d, bool (*f) (int)) {
cout << (f(d) ? "true" : "false");
}
//void f(const int &d, std::function<bool(int)> f) {
// cout << (f(d) ? "true" : "false");
//}
f(10, [](int e) -> bool { return e % 2 == 0; });
f(10, is_even);
even if I comment out second function and comment first, it would still work. But when I add template like this:
template<typename T>
void f(const T &d, bool (*f) (T)) {
cout << (f(d) ? "true" : "false");
}
Or this
template<typename T>
void f(const T &d, std::function<bool(T)> f) {
cout << (f(d) ? "true" : "false");
}
It says
no instance of function template "f" matches the argument list argument types are: (int, lambda []bool (int e)->bool)
If I use std::function, it also says
no instance of function template "f" matches the argument list argument types are: (int, bool (int i))
So the question is: How can I make this template work both with functions and with lambdas?
edit: I think I initially gave less information than needed. The thing is that I want to overload an operator several times with different functions like this:
template<typename T>
vector<T> operator|(const vector<T> &vec, void (*f) (T)) {
// ...
}
template<typename T>
vector<T> operator|(const vector<T> &vec, bool (*f) (T)) {
// ...
}
template<typename TIn, typename TOut>
vector<TOut> operator|(const vector<TIn> &vec, TOut (*f) (TIn)) {
// ...
}
Upvotes: 2
Views: 1372
Reputation: 3700
I know the situation where you simplify your problem to an extent where the proposed solution does not work for the original problem anymore, so here is a solution that works for your original problem:
What you want to do is you want to find the return type of whatever you pass to your function (be it a lambda or a function pointer) at compile time and then do different things based on that type. You can achieve that by introducing an intermediate function that finds the return type and then calls the real function.
template<typename T>
void real_foo(const vector<T> &vec, void (*f)(T)) {
cout << "void";
}
template<typename T>
void real_foo(const vector<T> &vec, bool (*f)(T)) {
cout << "bool";
}
template<typename TIn, typename TOut>
void real_foo(const vector<TIn> &vec, TOut (*f)(TIn)) {
cout << "generic";
}
template<typename TIn, typename F>
void operator|(const vector<TIn> &vec, F &&f) {
using TOut = decltype(f(declval<TIn>()));
TOut (*fp)(TIn) = f;
real_foo(vec, fp);
}
If you want to pass lambdas that are not not convertible to a function pointer (e.g. if they capture something) you need something a bit more elaborate (note that you don't need to use std::function
if you are only executing the lambda and not storing it).
template <typename TIn, typename F, typename TOut>
struct Foo {
static void foo(const vector<TIn> &vec, F &&f) {
cout << "generic";
}
};
template <typename TIn, typename F>
struct Foo<TIn, F, void> {
static void foo(const vector<TIn> &vec, F &&f) {
cout << "void";
}
};
template <typename TIn, typename F>
struct Foo<TIn, F, bool> {
static void foo(const vector<TIn> &vec, F &&f) {
cout << "bool";
}
};
template<typename TIn, typename F>
void operator|(const vector<TIn> &vec, F &&f) {
using TOut = decltype(f(declval<TIn>()));
Foo<TIn, F, TOut>::foo(vec, forward<F>(f));
}
Now you can use the operator the way you envisioned:
vec | [](int){};
vec | [](int){ return true; };
vec | [](int){ return 5; };
vec | is_even;
Upvotes: 0
Reputation: 38919
Since your using a lambda, I'd recommend going with your template<typename T>
void f(const T&, std::function<bool(T)>)
definition, that'll allow you to support capturing lambdas down the road if you so desire.
Your implementation of that is fine you just need to help C++ out by specifying instead of expecting it to deduce T
. This code works fine in Visual Studio 2013 and gcc 4.9.2:
#include <iostream>
#include <functional>
using namespace std;
static bool is_even(int i) {
return i % 2 == 0;
}
template<typename T>
void f(const T &d, function<bool(T)> f) {
cout << (f(d) ? "true" : "false");
}
int main() {
f<int>(10, [](int e){ return e % 2 == 0; });
f<int>(10, is_even);
return 0;
}
Note that I'm specifying the template type int
. The problem is that C++ doesn't know whether T
should be based on your first or second parameter. It's fine if they are the same type, but the compiler cannot deduce the parameter type of std::function
, so it cannot confirm the type T
is consistent. By passing in the type, the compiler no longer has to deduce it.
Upvotes: 0
Reputation: 7904
The issue is that implicit conversions are not checked during template argument deduction.
It seems like what you want is to turn off template argument deduction in the second parameter. You can do that by forcing T
be in a non-deduced context. We'll use the following non-deduced context specified in the standard.
5 The non-deduced contexts are:
(5.1) — The nested-name-specifier of a type that was specified using a qualified-id.
template <typename T> struct identity { using type = T; };
template <typename T> using identity_t = typename identity<T>::type;
template<typename T>
void f(const T &d, bool (*g) (identity_t<T>)) {
cout << (g(d) ? "true" : "false");
}
/* or */
template<typename T>
void f(const T &d, std::function<bool(identity_t<T>)> f) {
cout << (f(d) ? "true" : "false");
}
Both of them works for:
f(10, [](int e) -> bool { return e % 2 == 0; });
f(10, is_even);
Upvotes: 3
Reputation: 345
Here is an example how you could do it:
#include <cmath>
#include <functional>
#include <iomanip>
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T, typename F>
auto func(T && t, F && f)
-> typename enable_if<is_same<bool, typename result_of<F(T)>::type>::value, bool>::type
{
return f(t);
}
bool is_pi(double d)
{
// approximately
return d == 3.1415926;
}
int main()
{
cout << boolalpha << func(42, [](int answer){ return answer == 42; }) << endl;
cout << boolalpha << func(M_PI, is_pi);
return 0;
}
In the first place every kind of function pointer, function object or lambda is allowed. Then the enable_if decides if the template can be instantiated for the given type depending on if it supports a parameter of type T and returns a bool.
See here: ideone
Upvotes: 1
Reputation: 56547
Lambdas and std::function
are not of the same type as a function pointer, although they may be inter-convertible. However, in template type deduction, types are not implicitly converted, and that's why you have no valid matching.
You can force the type by explicitly specifying the template type
f<int>(10, [](int e) -> bool { return e % 2 == 0; });
In this case, the code will almost compile, but the compiler complains that you have an ambiguous definition of f
, since the pointer overload is an equally good match. So, keep only the std::function
overload, and manually specify the template type
#include <iostream>
#include <functional>
static bool is_even(int i) {
return i % 2 == 0;
}
template<typename T>
void f(const T &d, std::function<bool(T)> f) {
std::cout << (f(d) ? "true" : "false") << std::endl;
}
int main()
{
f<int>(10, [](int e) -> bool { return e % 2 == 0; });
f<int>(10, is_even);
}
Upvotes: 1
Reputation: 217145
You can do:
template<typename T, typename F>
void func(const T &d, F f) {
std::cout << (f(d) ? "true" : "false");
}
Upvotes: 3