2

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)) {
    // ...
}

6 Answers 6

3

You can do:

template<typename T, typename F>
void func(const T &d, F f) {
    std::cout << (f(d) ? "true" : "false");
}
Sign up to request clarification or add additional context in comments.

2 Comments

I've seen this solution. Only problem is that I want several functions with name 'func' and different function parameters (for example, function<bool(T)>, function<void(T)>, function<T(T)>).
@fakefeik You could make multiple typenames, or you could just help the compiler deduce T.
3

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);

Comments

1

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

7 Comments

Can you explain the -> operator in the templatized function? I've seen that in a lambda to specify return type. Is that what it's doing here?
It is called "trailing return type": Starting with C++11 you can write a function like auto main() -> int {} The auto in the beginning just tells the compiler that the return type will follow later. It is mostly used in templates, where the return type cannot be determined until after the declaration of the function's parameters. You may also use it for better readability though :-) In C++14 auto is actually sufficient without a trailing return type, as the compiler will determine the actual type for you. In lambdas the trailing type is used to explicitely state the return type.
+1 cause I love learning stuff. But wouldn't it have been easier just to do: enable_if_t<is_same<bool, result_of_t<F(T)>>::value, bool> func(T&& t, F&& f)
Easier maybe, but that is questionable. In my version someone knowing C++11 but new to the project would instantly spot the function's name and return type. The usual version hides the function's name in a load of template voodoo. (atleast without syntax highlighting...) Also if you have a lot of declarations one after another it looks way nicer when all of them start with auto and end in a trailing return type then having this long type in front of the function. But yeah, it is a matter of taste.
With template voodoo I mean your AND my version. And I call it voodoo because the template stuff is like 30 characters and then there are 4 characters function name... Also I kind of missed on the _t at the end of the helper types. Guess I learned something, too.
|
1

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);
}

3 Comments

What if I want to overload an operator? How can I use this <int> thing there?
You can use decltype(variable) to instantiate the template with the type of variable, like f<decltype(variable)>(...). I still don't get it exactly why Jarod42 solution is not ok for you, as it is definitely simpler.
@vsoftco Arg! I didn't see your edit before I posted my answer! I believe this is the right solution. +1 for beating me to it.
0

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.

Comments

0

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;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.